From 104b8f93e46d77557a0cc5a7720714c960e0b043 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2020 17:04:48 -0700 Subject: [PATCH 01/41] Bump spring.version in /aws-serverless-java-container-spring (#319) Bumps `spring.version` from 5.1.9.RELEASE to 5.2.3.RELEASE. Updates `spring-webmvc` from 5.1.9.RELEASE to 5.2.3.RELEASE - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.1.9.RELEASE...v5.2.3.RELEASE) Updates `spring-test` from 5.1.9.RELEASE to 5.2.3.RELEASE - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.1.9.RELEASE...v5.2.3.RELEASE) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- aws-serverless-java-container-spring/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index 01bab3056..53bdbd702 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -16,7 +16,7 @@ - 5.1.9.RELEASE + 5.2.3.RELEASE 1.5.22.RELEASE 5.1.5.RELEASE From 69ce1b485abf7c05a79c2aeada56a18b0216e60e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2020 17:05:14 -0700 Subject: [PATCH 02/41] Bump spring-webflux in /aws-serverless-java-container-springboot2 (#318) Bumps [spring-webflux](https://github.com/spring-projects/spring-framework) from 5.1.9.RELEASE to 5.2.0.RELEASE. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.1.9.RELEASE...v5.2.0.RELEASE) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- aws-serverless-java-container-springboot2/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-serverless-java-container-springboot2/pom.xml b/aws-serverless-java-container-springboot2/pom.xml index 90c98f009..0ec0e07c1 100644 --- a/aws-serverless-java-container-springboot2/pom.xml +++ b/aws-serverless-java-container-springboot2/pom.xml @@ -15,7 +15,7 @@ 1.5-SNAPSHOT - 5.1.9.RELEASE + 5.2.0.RELEASE 5.1.6.RELEASE 2.1.8.RELEASE From 0f9fe5c0129d22369b4435a6f9ad9395db7aa4ad Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Sat, 28 Mar 2020 23:06:46 -0700 Subject: [PATCH 03/41] ci: Fixing Spring build to use 5.2 as latest --- .github/workflows/continuous-integration-workflow.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 1dfb971d9..d0de712f8 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -45,17 +45,15 @@ jobs: steps: - uses: actions/checkout@v2 - name: Build latest - run: ./gha_build.sh spring true true + # we reduce the minCoverage for this run because it will skip the SpringBoot 1.5 tests since they are no longer compatible with + # Spring core 5.2 and above. SpringBoot 1.5 is deprecated + run: ./gha_build.sh spring true true -Djacoco.minCoverage=0.4 - name: Build Spring 4.3 run: ./gha_build.sh spring false false -Dspring.version=4.3.25.RELEASE -Dspring-security.version=4.2.13.RELEASE - name: Build Spring 5.0 run: ./gha_build.sh spring false false -Dspring.version=5.0.15.RELEASE -Dspring-security.version=5.0.13.RELEASE - name: Build Spring 5.1 run: ./gha_build.sh spring false false -Dspring.version=5.1.14.RELEASE -Dspring-security.version=5.1.8.RELEASE - - name: Build Spring 5.2 - # we reduce the minCoverage for this run because it will skip the SpringBoot 1.5 tests since they are no longer compatible with - # Spring core 5.2 and above. SpringBoot 1.5 is deprecated - run: ./gha_build.sh spring false false -Dspring.version=5.2.5.RELEASE -Dspring-security.version=5.2.2.RELEASE -Djacoco.minCoverage=0.4 build_springboot2: name: Build and test SpringBoot 2 From ac367455e571c9707453cf547c03a18b16781fd3 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Sat, 28 Mar 2020 23:24:30 -0700 Subject: [PATCH 04/41] chore(deps): Bump Spring 5.1 path release to address a security vulnerability --- .github/workflows/continuous-integration-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index d0de712f8..9116a5751 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -51,7 +51,7 @@ jobs: - name: Build Spring 4.3 run: ./gha_build.sh spring false false -Dspring.version=4.3.25.RELEASE -Dspring-security.version=4.2.13.RELEASE - name: Build Spring 5.0 - run: ./gha_build.sh spring false false -Dspring.version=5.0.15.RELEASE -Dspring-security.version=5.0.13.RELEASE + run: ./gha_build.sh spring false false -Dspring.version=5.0.16.RELEASE -Dspring-security.version=5.0.14.RELEASE - name: Build Spring 5.1 run: ./gha_build.sh spring false false -Dspring.version=5.1.14.RELEASE -Dspring-security.version=5.1.8.RELEASE From 421f3906e3f7749fb4050219e738cec4564f7f57 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Sat, 28 Mar 2020 23:51:11 -0700 Subject: [PATCH 05/41] chore(deps): Fixing usual spring dependency mess with exlusions out of the spring-security package used in the tests --- .../continuous-integration-workflow.yml | 6 +-- .../pom.xml | 38 +++++++++++++++++-- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 9116a5751..8626c5de5 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -63,11 +63,9 @@ jobs: - name: Build latest run: ./gha_build.sh springboot2 true true - name: Build Spring Boot 2.0 - run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.0.9.RELEASE -Dspring.version=5.0.13.RELEASE -Dspringsecurity.version=5.0.12.RELEASE + run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.0.9.RELEASE -Dspring.version=5.0.16.RELEASE -Dspringsecurity.version=5.0.14.RELEASE - name: Build Spring Boot 2.1 - run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.1.10.RELEASE -Dspring.version=5.1.11.RELEASE -Dspringsecurity.version=5.1.7.RELEASE - - name: Build Spring Boot 2.2 - run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.2.1.RELEASE -Dspring.version=5.2.1.RELEASE -Dspringsecurity.version=5.2.1.RELEASE + run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.1.10.RELEASE -Dspring.version=5.1.14.RELEASE -Dspringsecurity.version=5.1.8.RELEASE build_struts2: name: Build and test Struts 2 diff --git a/aws-serverless-java-container-springboot2/pom.xml b/aws-serverless-java-container-springboot2/pom.xml index 0ec0e07c1..af206a772 100644 --- a/aws-serverless-java-container-springboot2/pom.xml +++ b/aws-serverless-java-container-springboot2/pom.xml @@ -15,9 +15,9 @@ 1.5-SNAPSHOT - 5.2.0.RELEASE - 5.1.6.RELEASE - 2.1.8.RELEASE + 5.2.5.RELEASE + 5.2.2.RELEASE + 2.2.6.RELEASE 1.8 1.8 @@ -53,12 +53,44 @@ org.springframework.security spring-security-config ${springsecurity.version} + + + org.springframework + spring-context + + + org.springframework + spring-beans + + + org.springframework + spring-core + + test org.springframework.security spring-security-web ${springsecurity.version} + + + org.springframework + spring-core + + + org.springframework + spring-web + + + org.springframework + spring-beans + + + org.springframework + spring-context + + test From 787c353ecb543bea61613441d6043f83d00d5d7f Mon Sep 17 00:00:00 2001 From: Eran Medan Date: Sun, 29 Mar 2020 23:47:17 -0400 Subject: [PATCH 06/41] Fix for issue #317 (#323) * fix issue 317 - use charset from request * update dependencies * update build dependencies, remove spring boot 2.0.x * restoring ci config Co-authored-by: Stefano Buliani <2996317+sapessi@users.noreply.github.com> --- .../continuous-integration-workflow.yml | 6 +++--- .../internal/servlet/AwsHttpServletResponse.java | 8 +++++++- .../proxy/spring/SpringAwsProxyTest.java | 16 ++++++++++++++++ .../proxy/spring/SpringBootAppTest.java | 13 +++++++++++++ .../resources/archetype-resources/build.gradle | 4 ++-- .../main/resources/archetype-resources/pom.xml | 2 +- .../resources/archetype-resources/build.gradle | 2 +- .../main/resources/archetype-resources/pom.xml | 2 +- samples/micronaut/pet-store/build.gradle | 2 +- samples/spring/pet-store/build.gradle | 4 ++-- samples/springboot2/pet-store/build.gradle | 4 ++-- samples/springboot2/pet-store/pom.xml | 2 +- 12 files changed, 50 insertions(+), 15 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 9116a5751..77e2da0b2 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -63,11 +63,11 @@ jobs: - name: Build latest run: ./gha_build.sh springboot2 true true - name: Build Spring Boot 2.0 - run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.0.9.RELEASE -Dspring.version=5.0.13.RELEASE -Dspringsecurity.version=5.0.12.RELEASE + run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.0.9.RELEASE -Dspring.version=5.0.16.RELEASE -Dspringsecurity.version=5.0.14.RELEASE - name: Build Spring Boot 2.1 - run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.1.10.RELEASE -Dspring.version=5.1.11.RELEASE -Dspringsecurity.version=5.1.7.RELEASE + run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.1.12.RELEASE -Dspring.version=5.1.13.RELEASE -Dspringsecurity.version=5.1.8.RELEASE - name: Build Spring Boot 2.2 - run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.2.1.RELEASE -Dspring.version=5.2.1.RELEASE -Dspringsecurity.version=5.2.1.RELEASE + run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.2.3.RELEASE -Dspring.version=5.2.3.RELEASE -Dspringsecurity.version=5.2.2.RELEASE build_struts2: name: Build and test Struts 2 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 da695c509..153cdb91e 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 @@ -419,7 +419,13 @@ public void flushBuffer() throws IOException { if (null != writer) { writer.flush(); } - responseBody = new String(bodyOutputStream.toByteArray(), LambdaContainerHandler.getContainerConfig().getDefaultContentCharset()); + String charset = characterEncoding; + + if(charset == null) { + charset = LambdaContainerHandler.getContainerConfig().getDefaultContentCharset(); + } + + responseBody = new String(bodyOutputStream.toByteArray(), charset); log.debug("Response buffer flushed with {} bytes, latch={}", responseBody.length(), writersCountDownLatch.getCount()); isCommitted = true; writersCountDownLatch.countDown(); 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 b9cb728d6..ebd0f2fd3 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 @@ -45,6 +45,7 @@ public class SpringAwsProxyTest { private static final String CUSTOM_HEADER_KEY = "x-custom-header"; private static final String CUSTOM_HEADER_VALUE = "my-custom-value"; private static final String AUTHORIZER_PRINCIPAL_ID = "test-principal-" + UUID.randomUUID().toString(); + private static final String UNICODE_VALUE = "שלום לכולם"; @Autowired private ObjectMapper objectMapper; @@ -249,6 +250,21 @@ public void responseBody_responseWriter_validBody() throws JsonProcessingExcepti validateSingleValueModel(output, CUSTOM_HEADER_VALUE); } + @Test + public void responseBody_responseWriter_validBody_UTF() throws JsonProcessingException { + SingleValueModel singleValueModel = new SingleValueModel(); + singleValueModel.setValue(UNICODE_VALUE); + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/json-body", "POST") + .header("Content-Type", "application/json; charset=UTF-8") + .body(objectMapper.writeValueAsString(singleValueModel)) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertNotNull(output.getBody()); + validateSingleValueModel(output, UNICODE_VALUE); + } + @Test public void statusCode_responseStatusCode_customStatusCode() { AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/status-code", "GET") 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 d453dfe43..2e029e921 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 @@ -160,6 +160,19 @@ public void utf8_returnUtf8String_expectCorrectHeaderMediaAndCharset() { assertTrue(output.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).contains("charset=UTF-8")); } + @Test + public void utf8_returnUtf8String_expectCorrectHeaderMediaAndCharsetNoDefault() { + + AwsProxyRequest request = new AwsProxyRequestBuilder("/test/utf8", "GET") + .header("Content-Type", "application/json; charset=UTF-8") + .build(); + AwsProxyResponse output = handler.handleRequest(request, context); + 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-spring-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/build.gradle index e67eb52e4..6447fe4e8 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,8 +7,8 @@ repositories { dependencies { compile ( - 'org.springframework:spring-webmvc:5.1.9.RELEASE', - 'org.springframework:spring-context:5.1.9.RELEASE', + 'org.springframework:spring-webmvc:5.1.13.RELEASE', + 'org.springframework:spring-context:5.1.13.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', 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 a6a574c8b..2f6d386d7 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.9.RELEASE + 5.1.13.RELEASE 4.12 2.8.2 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 e85ac40d8..9d9d17af1 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.8.RELEASE', + 'org.springframework.boot:spring-boot-starter-web:2.1.12.RELEASE', 'com.amazonaws.serverless:aws-serverless-java-container-springboot2:[1.0,)', 'io.symphonia:lambda-logging:1.0.1' ) 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 3dfaa1235..8d1fc100c 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.8.RELEASE + 2.1.12.RELEASE diff --git a/samples/micronaut/pet-store/build.gradle b/samples/micronaut/pet-store/build.gradle index 94f3d1b5f..8ed45b3d7 100644 --- a/samples/micronaut/pet-store/build.gradle +++ b/samples/micronaut/pet-store/build.gradle @@ -3,7 +3,7 @@ plugins { id "com.github.johnrengelman.shadow" version "5.0.0" id "application" id "net.ltgt.apt-eclipse" version "0.21" - id "org.springframework.boot" version "2.1.8.RELEASE" + id "org.springframework.boot" version "2.1.12.RELEASE" id "io.spring.dependency-management" version "1.0.6.RELEASE" } diff --git a/samples/spring/pet-store/build.gradle b/samples/spring/pet-store/build.gradle index 7d7a74c44..a8e815fbb 100644 --- a/samples/spring/pet-store/build.gradle +++ b/samples/spring/pet-store/build.gradle @@ -7,8 +7,8 @@ repositories { dependencies { compile ( - 'org.springframework:spring-webmvc:5.1.9.RELEASE', - 'org.springframework:spring-context:5.1.9.RELEASE', + 'org.springframework:spring-webmvc:5.1.13.RELEASE', + 'org.springframework:spring-context:5.1.13.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', diff --git a/samples/springboot2/pet-store/build.gradle b/samples/springboot2/pet-store/build.gradle index 65826e900..fd4e81817 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.8.RELEASE' + id 'org.springframework.boot' version '2.1.12.RELEASE' } apply plugin: 'java' @@ -11,7 +11,7 @@ repositories { dependencies { compile ( - implementation('org.springframework.boot:spring-boot-starter-web:2.1.8.RELEASE') { + implementation('org.springframework.boot:spring-boot-starter-web:2.1.12.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/springboot2/pet-store/pom.xml b/samples/springboot2/pet-store/pom.xml index 547a7ead1..15bf3e671 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.8.RELEASE + 2.1.12.RELEASE From 7dcede668d752d545aad597b8ce0fa7f13ec2238 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Mon, 30 Mar 2020 19:48:37 -0700 Subject: [PATCH 07/41] test: Fixed Spring security tests for SpringBoot 2, added validation tests and updated servlet tests to use the new servletApplication option --- .../proxy/spring/SecurityAppTest.java | 7 --- .../proxy/spring/ServletAppTest.java | 39 ++++++++++++++ .../spring/securityapp/LambdaHandler.java | 3 +- .../spring/securityapp/MessageController.java | 7 +-- .../securityapp/SecurityApplication.java | 14 +++++ .../spring/securityapp/SecurityConfig.java | 53 +++++++------------ .../securityapp/ServletApplication.java | 13 ----- .../spring/servletapp/LambdaHandler.java | 9 +++- .../spring/servletapp/MessageController.java | 20 ++++++- .../spring/servletapp/ServletApplication.java | 5 +- .../proxy/spring/servletapp/UserData.java | 50 +++++++++++++++++ 11 files changed, 157 insertions(+), 63 deletions(-) create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityApplication.java delete 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/servletapp/UserData.java 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 index 34a593fd2..e9682d4c4 100644 --- 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 @@ -1,14 +1,11 @@ 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; @@ -39,8 +36,4 @@ public void helloRequest_withAuth_respondsWithSingleMessage() { 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 4d5797e5f..60cf83129 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,14 +1,21 @@ 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.SecurityConfig; import com.amazonaws.serverless.proxy.spring.servletapp.LambdaHandler; import com.amazonaws.serverless.proxy.spring.servletapp.MessageController; +import com.amazonaws.serverless.proxy.spring.servletapp.UserData; +import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; import org.junit.Test; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; + import static org.junit.Assert.assertEquals; public class ServletAppTest { @@ -22,4 +29,36 @@ public void helloRequest_respondsWithSingleMessage() { AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); Assert.assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); } + + @Test + public void validateRequest_invalidData_respondsWith400() { + UserData ud = new UserData(); + AwsProxyRequest req = new AwsProxyRequestBuilder("/validate", "POST") + .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .body(ud) + .build(); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + try { + System.out.println(LambdaContainerHandler.getObjectMapper().writeValueAsString(resp)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + assertEquals("3", resp.getBody()); + assertEquals(400, resp.getStatusCode()); + + UserData ud2 = new UserData(); + ud2.setFirstName("Test"); + ud2.setLastName("Test"); + ud2.setEmail("Test"); + req = new AwsProxyRequestBuilder("/validate", "POST") + .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .body(ud2) + .build(); + System.out.println(req.getBody()); + resp = handler.handleRequest(req, lambdaContext); + assertEquals("1", resp.getBody()); + assertEquals(400, resp.getStatusCode()); + } } 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 index 75dc158ab..ae8ba21ac 100644 --- 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 @@ -1,7 +1,6 @@ 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; @@ -13,7 +12,7 @@ public class LambdaHandler implements RequestHandler hello() { + return Mono.just(HELLO_MESSAGE); } } diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityApplication.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityApplication.java new file mode 100644 index 000000000..cafcd4000 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityApplication.java @@ -0,0 +1,14 @@ +package com.amazonaws.serverless.proxy.spring.securityapp; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.web.reactive.config.EnableWebFlux; + +@SpringBootApplication +@EnableWebFluxSecurity +@EnableWebFlux +@Import(SecurityConfig.class) +public class SecurityApplication { +} 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 index 128ae3dd8..5317f9e54 100644 --- 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 @@ -1,7 +1,5 @@ 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; @@ -10,52 +8,37 @@ 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(); +public class SecurityConfig +{ + public static final String USERNAME = "admin"; + public static final String PASSWORD = "{noop}password"; + private static BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); @Bean public SecurityWebFilterChain securitygWebFilterChain( ServerHttpSecurity http) { return http.authorizeExchange() - .anyExchange().authenticated() - .and().httpBasic() - .and().build(); + .anyExchange().authenticated().and().csrf().disable() + .httpBasic() + .and().anonymous().disable().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(); + public static BCryptPasswordEncoder passwordEncoder() { + return passwordEncoder; } @Bean - public PasswordEncoder passwordEncoder() { - return pEncoder; + public MapReactiveUserDetailsService userDetailsService() { + UserDetails user = User + .withUsername(USERNAME) + .password(passwordEncoder.encode(PASSWORD)) + .roles("USER") + .build(); + return new MapReactiveUserDetailsService(user); } - - /*@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}", ""); - } - } - }*/ -} +} \ No newline at end of file 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 deleted file mode 100644 index 2f1def12a..000000000 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/ServletApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -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 7faf9b1b8..8b1d8fff8 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,10 +1,12 @@ package com.amazonaws.serverless.proxy.spring.servletapp; import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.InitializationWrapper; 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; @@ -13,7 +15,12 @@ public class LambdaHandler implements RequestHandler validateBody(@RequestBody @Valid UserData userData, Errors errors) { + if (errors != null && errors.hasErrors()) { + return ResponseEntity.badRequest().body(errors.getErrorCount() + ""); + } + return ResponseEntity.ok(VALID_MESSAGE); + } } 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 927dd6ce5..07ddbab43 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 @@ -2,6 +2,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; @SpringBootApplication(exclude = { org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration.class, @@ -9,5 +11,6 @@ org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.class, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class }) -public class ServletApplication extends SpringBootServletInitializer { +@Import(MessageController.class) +public class ServletApplication { } diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/UserData.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/UserData.java new file mode 100644 index 000000000..e180c1738 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/UserData.java @@ -0,0 +1,50 @@ +package com.amazonaws.serverless.proxy.spring.servletapp; + + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +public class UserData { + @NotBlank + private String firstName; + @NotBlank + private String lastName; + @NotNull @Email + private String email; + private String error; + + public UserData() { + + } + + public UserData(String err) { + error = err; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getError() { return error; } +} From 37613114cd155c17d00500fdb026eaa0eb993d6e Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Mon, 30 Mar 2020 19:51:33 -0700 Subject: [PATCH 08/41] fix: Avoid flushing the response buffer if we are dispatching the request asynchronously. This was causing race conditions in the SpringBoot 2 WebFlux implementation - requests that had to run through security or validation filters took longer and the library flushed an empty request, which caused the status code to default to 200. This fix addresses issues #279, #304, and #306 --- .../internal/servlet/AwsLambdaServletContainerHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 95af74e67..6c425b8ef 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 @@ -149,8 +149,8 @@ protected void doFilter(HttpServletRequest request, HttpServletResponse response FilterChain chain = getFilterChain(request, servlet); chain.doFilter(request, response); - // if for some reason the response wasn't flushed yet, we force it here. - if (!response.isCommitted()) { + // if for some reason the response wasn't flushed yet, we force it here unless it's being processed asynchronously (WebFlux) + if (!response.isCommitted() && request.getDispatcherType() != DispatcherType.ASYNC) { response.flushBuffer(); } } From cc9eba8f595b2decee8b78c4e0929d273a1c1cdc Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Mon, 30 Mar 2020 19:53:07 -0700 Subject: [PATCH 09/41] chore(deps): Bump spring dependency version and added webmvc optional dependency to truly support Servlet-only server --- .../pom.xml | 58 ++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/aws-serverless-java-container-springboot2/pom.xml b/aws-serverless-java-container-springboot2/pom.xml index af206a772..ef59afb50 100644 --- a/aws-serverless-java-container-springboot2/pom.xml +++ b/aws-serverless-java-container-springboot2/pom.xml @@ -1,5 +1,6 @@ - + aws-serverless-java-container com.amazonaws.serverless @@ -16,9 +17,8 @@ 5.2.5.RELEASE - 5.2.2.RELEASE 2.2.6.RELEASE - + 5.2.2.RELEASE 1.8 1.8 @@ -49,6 +49,24 @@ ${springboot.version} true + + org.springframework + spring-webmvc + ${spring.version} + true + + + org.springframework + spring-aop + + + org.springframework + spring-expression + + + test + + org.springframework.security spring-security-config @@ -93,6 +111,18 @@ test + + javax.validation + validation-api + 2.0.1.Final + test + + + org.hibernate + hibernate-validator + 6.1.2.Final + test + org.jetbrains annotations @@ -133,16 +163,18 @@ true - - BUNDLE - - - INSTRUCTION - COVEREDRATIO - ${jacoco.minCoverage} - - - + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.minCoverage} + + + + From b7bd31bc502c853cf24b5ffc9454730a52f1ba9f Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Mon, 30 Mar 2020 19:55:32 -0700 Subject: [PATCH 10/41] feat: New application type parameter to SpringBootLambdaContainerHandler that tells the framework whether to start a reactive or servlet-based embedded server. Also added a new servletApplication method to the builder object. --- .../SpringBootLambdaContainerHandler.java | 45 +++++++++++++------ .../spring/SpringBootProxyHandlerBuilder.java | 10 ++++- ...rlessServletEmbeddedServerFactoryTest.java | 4 +- 3 files changed, 43 insertions(+), 16 deletions(-) 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 5b56e58b1..e33395360 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 @@ -24,10 +24,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; +import org.springframework.web.context.ConfigurableWebApplicationContext; import javax.servlet.Servlet; +import javax.servlet.ServletRegistration; import java.util.concurrent.CountDownLatch; /** @@ -44,6 +50,8 @@ public class SpringBootLambdaContainerHandler extends private final Class springBootInitializer; private static final Logger log = LoggerFactory.getLogger(SpringBootLambdaContainerHandler.class); private String[] springProfiles = null; + private WebApplicationType springWebApplicationType; + private ConfigurableApplicationContext applicationContext; private static SpringBootLambdaContainerHandler instance; @@ -90,6 +98,7 @@ public static SpringBootLambdaContainerHandler requestTypeClass, Class responseTypeClass, @@ -98,11 +107,13 @@ public SpringBootLambdaContainerHandler(Class requestTypeClass, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, Class springBootInitializer, - InitializationWrapper init) { + InitializationWrapper init, + WebApplicationType applicationType) { super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); Timer.start("SPRINGBOOT2_CONTAINER_HANDLER_CONSTRUCTOR"); initialized = false; this.springBootInitializer = springBootInitializer; + springWebApplicationType = applicationType; setInitializationWrapper(init); SpringBootLambdaContainerHandler.setInstance(this); @@ -150,14 +161,15 @@ public void initialize() throws ContainerInitializationException { Timer.start("SPRINGBOOT2_COLD_START"); - SpringApplication app = new SpringApplication(getEmbeddedContainerClasses()); - if (springProfiles != null && springProfiles.length > 0) { - ConfigurableEnvironment springEnv = new StandardEnvironment(); - springEnv.setActiveProfiles(springProfiles); - app.setEnvironment(springEnv); + SpringApplicationBuilder builder = new SpringApplicationBuilder(getEmbeddedContainerClasses()) + .web(springWebApplicationType); // .REACTIVE, .SERVLET + if (springProfiles != null) { + builder.profiles(springProfiles); + } + applicationContext = builder.run(); + if (springWebApplicationType == WebApplicationType.SERVLET) { + ((AnnotationConfigServletWebServerApplicationContext)applicationContext).setServletContext(getServletContext()); } - - app.run(); initialized = true; Timer.stop("SPRINGBOOT2_COLD_START"); @@ -165,12 +177,17 @@ public void initialize() 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] = ServerlessReactiveServletEmbeddedServerFactory.class; - } catch (ClassNotFoundException e) { + if (springWebApplicationType == WebApplicationType.REACTIVE) { + 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] = ServerlessReactiveServletEmbeddedServerFactory.class; + } catch (ClassNotFoundException e) { + springWebApplicationType = WebApplicationType.SERVLET; + classes[0] = ServerlessServletEmbeddedServerFactory.class; + } + } else { classes[0] = ServerlessServletEmbeddedServerFactory.class; } 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 index 943cc69ff..bd757ea03 100644 --- 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 @@ -5,6 +5,7 @@ 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.boot.WebApplicationType; public final class SpringBootProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< AwsProxyRequest, @@ -14,6 +15,7 @@ public final class SpringBootProxyHandlerBuilder extends ServletLambdaContainerH SpringBootProxyHandlerBuilder> { private Class springBootInitializer; private String[] profiles; + private WebApplicationType applicationType = WebApplicationType.REACTIVE; @Override protected SpringBootProxyHandlerBuilder self() { @@ -31,6 +33,11 @@ public SpringBootProxyHandlerBuilder profiles(String... profiles) { return self(); } + public SpringBootProxyHandlerBuilder servletApplication() { + this.applicationType = WebApplicationType.SERVLET; + return self(); + } + @Override public SpringBootLambdaContainerHandler build() throws ContainerInitializationException { validate(); @@ -45,7 +52,8 @@ public SpringBootLambdaContainerHandler build securityContextWriter, exceptionHandler, springBootInitializer, - initializationWrapper + initializationWrapper, + applicationType ); if (profiles != null) { handler.activateSpringProfiles(profiles); 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 010bcbdcf..11779420c 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 @@ -10,6 +10,7 @@ import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; import org.junit.Test; +import org.springframework.boot.WebApplicationType; import org.springframework.boot.web.servlet.ServletContextInitializer; import javax.servlet.ServletContext; @@ -26,7 +27,8 @@ public class ServerlessServletEmbeddedServerFactoryTest { new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler(), null, - new InitializationWrapper() + new InitializationWrapper(), + WebApplicationType.REACTIVE ); public ServerlessServletEmbeddedServerFactoryTest() throws ContainerInitializationException { From ceea3370c1d60cd82ea7f2bce925844237bc3640 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Mon, 30 Mar 2020 20:31:13 -0700 Subject: [PATCH 11/41] test: Fixed UTF-8 encoding test --- .../amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 ebd0f2fd3..cbb9a3a15 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 @@ -6,6 +6,7 @@ import com.amazonaws.serverless.proxy.internal.servlet.AwsServletContext; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.serverless.proxy.spring.echoapp.EchoResource; import com.amazonaws.serverless.proxy.spring.echoapp.EchoSpringAppConfig; import com.amazonaws.serverless.proxy.spring.echoapp.RestControllerAdvice; @@ -258,11 +259,12 @@ public void responseBody_responseWriter_validBody_UTF() throws JsonProcessingExc .header("Content-Type", "application/json; charset=UTF-8") .body(objectMapper.writeValueAsString(singleValueModel)) .build(); - + LambdaContainerHandler.getContainerConfig().setDefaultContentCharset("UTF-8"); AwsProxyResponse output = handler.proxy(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertNotNull(output.getBody()); validateSingleValueModel(output, UNICODE_VALUE); + LambdaContainerHandler.getContainerConfig().setDefaultContentCharset(ContainerConfig.DEFAULT_CONTENT_CHARSET); } @Test From 3043828d261567d33ee835f194e4a83d8e5eeb36 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Mon, 30 Mar 2020 20:31:47 -0700 Subject: [PATCH 12/41] ci: Fixed dependencies for CI run on SpringBoot 2 --- .../continuous-integration-workflow.yml | 4 +--- .../pom.xml | 22 +++++++++++++++++++ .../spring/securityapp/SecurityConfig.java | 2 +- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 77e2da0b2..51afca03e 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -66,9 +66,7 @@ jobs: run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.0.9.RELEASE -Dspring.version=5.0.16.RELEASE -Dspringsecurity.version=5.0.14.RELEASE - name: Build Spring Boot 2.1 run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.1.12.RELEASE -Dspring.version=5.1.13.RELEASE -Dspringsecurity.version=5.1.8.RELEASE - - name: Build Spring Boot 2.2 - run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.2.3.RELEASE -Dspring.version=5.2.3.RELEASE -Dspringsecurity.version=5.2.2.RELEASE - + build_struts2: name: Build and test Struts 2 runs-on: ubuntu-latest diff --git a/aws-serverless-java-container-springboot2/pom.xml b/aws-serverless-java-container-springboot2/pom.xml index ef59afb50..3144a3007 100644 --- a/aws-serverless-java-container-springboot2/pom.xml +++ b/aws-serverless-java-container-springboot2/pom.xml @@ -42,6 +42,16 @@ spring-boot ${springboot.version} true + + + org.springframework + spring-context + + + org.springframework + spring-core + + org.springframework.boot @@ -49,6 +59,18 @@ ${springboot.version} true + + org.springframework + spring-core + ${spring.version} + true + + + org.springframework + spring-context + ${spring.version} + true + org.springframework spring-webmvc 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 index 5317f9e54..d83b81db6 100644 --- 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 @@ -24,7 +24,7 @@ public SecurityWebFilterChain securitygWebFilterChain( return http.authorizeExchange() .anyExchange().authenticated().and().csrf().disable() .httpBasic() - .and().anonymous().disable().build(); + .and().build(); } @Bean From b7f894035422e1b3866cc0d5a7e29ce9823147b0 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Mon, 30 Mar 2020 20:43:18 -0700 Subject: [PATCH 13/41] ci: More Spring dependency convergence issues during CI --- .../continuous-integration-workflow.yml | 2 +- .../pom.xml | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 51afca03e..8166497e4 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -66,7 +66,7 @@ jobs: run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.0.9.RELEASE -Dspring.version=5.0.16.RELEASE -Dspringsecurity.version=5.0.14.RELEASE - name: Build Spring Boot 2.1 run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.1.12.RELEASE -Dspring.version=5.1.13.RELEASE -Dspringsecurity.version=5.1.8.RELEASE - + build_struts2: name: Build and test Struts 2 runs-on: ubuntu-latest diff --git a/aws-serverless-java-container-springboot2/pom.xml b/aws-serverless-java-container-springboot2/pom.xml index 3144a3007..316a883b3 100644 --- a/aws-serverless-java-container-springboot2/pom.xml +++ b/aws-serverless-java-container-springboot2/pom.xml @@ -106,6 +106,14 @@ org.springframework spring-core + + org.springframework + spring-expression + + + org.springframework + spring-aop + test @@ -130,6 +138,14 @@ org.springframework spring-context + + org.springframework + spring-expression + + + org.springframework + spring-aop + test From 8c98ab53af45bff0bc8ee36f34965e4183d8f780 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Mon, 30 Mar 2020 21:11:21 -0700 Subject: [PATCH 14/41] fix: Added null-check on getServerName in case the multi-value headers property is null. Unlikely outside of tests but better safe than sorry. This addresses #327 --- .../servlet/AwsProxyHttpServletRequest.java | 8 +++++--- .../AwsProxyHttpServletRequestTest.java | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 3 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 8d58850cf..4c69b535e 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 @@ -558,9 +558,11 @@ public String getServerName() { region = "us-east-1"; } - String hostHeader = request.getMultiValueHeaders().getFirst(HOST_HEADER_NAME); - if (hostHeader != null && SecurityUtils.isValidHost(hostHeader, request.getRequestContext().getApiId(), region)) { - return hostHeader; + if (request.getMultiValueHeaders() != null && request.getMultiValueHeaders().containsKey(HOST_HEADER_NAME)) { + String hostHeader = request.getMultiValueHeaders().getFirst(HOST_HEADER_NAME); + if (SecurityUtils.isValidHost(hostHeader, request.getRequestContext().getApiId(), region)) { + return hostHeader; + } } return new StringBuilder().append(request.getRequestContext().getApiId()) 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 c73e64ab9..1e1088226 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 @@ -521,6 +521,24 @@ public void getServerPort_invalidCustomPortFromHeader_expectDefaultPort() { assertEquals(443, req.getServerPort()); } + @Test + public void serverName_emptyHeaders_doesNotThrowNullPointer() { + AwsProxyRequest proxyReq = new AwsProxyRequestBuilder("/test", "GET").build(); + proxyReq.setMultiValueHeaders(null); + HttpServletRequest servletReq = getRequest(proxyReq, null, null); + String serverName = servletReq.getServerName(); + assertTrue(serverName.startsWith("null.execute-api")); + } + + @Test + public void serverName_hostHeader_returnsHostHeaderOnly() { + AwsProxyRequest proxyReq = new AwsProxyRequestBuilder("/test", "GET") + .header(HttpHeaders.HOST, "testapi.com").build(); + LambdaContainerHandler.getContainerConfig().addCustomDomain("testapi.com"); + HttpServletRequest servletReq = getRequest(proxyReq, null, null); + String serverName = servletReq.getServerName(); + assertEquals("testapi.com", serverName); + } private AwsProxyRequest getRequestWithHeaders() { return new AwsProxyRequestBuilder("/hello", "GET") From c05f653a7a4c49f8b6e084f381b433136b70475f Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 31 Mar 2020 10:31:58 -0700 Subject: [PATCH 15/41] fix: Changed servlet initialization mechanism so that servlet that requests load on startup are initialized right away, as part of the initialization() method call in LambdaServletContainerHandler. Also centralized the lazy Servlet initialization to the ServletExecutionFilter so that we don't have code scattered all around. This begins to address #287 --- .../AwsLambdaServletContainerHandler.java | 23 ++++++++++- .../internal/servlet/AwsServletContext.java | 37 +++++++----------- .../servlet/AwsServletRegistration.java | 23 ++++++++--- .../internal/servlet/FilterChainManager.java | 39 +++++++++++-------- .../proxy/internal/servlet/FilterHolder.java | 3 ++ .../spring/SpringLambdaContainerHandler.java | 4 ++ .../spring/echoapp/EchoSpringAppConfig.java | 7 +--- .../pom.xml | 1 - .../SpringBootLambdaContainerHandler.java | 10 ++++- 9 files changed, 94 insertions(+), 53 deletions(-) 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 6c425b8ef..6e5249ccd 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 @@ -12,6 +12,7 @@ */ package com.amazonaws.serverless.proxy.internal.servlet; +import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.ExceptionHandler; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.RequestReader; @@ -28,7 +29,8 @@ import javax.servlet.http.HttpServletResponseWrapper; import java.io.IOException; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; /** @@ -155,6 +157,25 @@ protected void doFilter(HttpServletRequest request, HttpServletResponse response } } + @Override + public void initialize() throws ContainerInitializationException { + // we expect all servlets to be wrapped in an AwsServletRegistration + ArrayList registrations = new ArrayList<>((Collection)getServletContext().getServletRegistrations().values()); + registrations.sort(AwsServletRegistration::compareTo); + for (AwsServletRegistration r : registrations) { + if (r.getLoadOnStartup() == -1) { // skip Servlets that can be lazily loaded + continue; + } + try { + if (r.getServlet() != null) { + r.getServlet().init(r.getServletConfig()); + } + } catch (ServletException e) { + throw new ContainerInitializationException("Could not initialize servlet " + r.getName(), e); + } + } + } + //------------------------------------------------------------- // Inner Class - //------------------------------------------------------------- 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 97e866e78..cf2e3920b 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 @@ -185,25 +185,21 @@ 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; + 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; } - 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(); - } + if (i == regParts.length - 1 && (regParts[i].equals(pathParts[i]) || "*".equals(regParts[i]))) { + return reg.getServlet(); } - } catch (ServletException e) { - return null; } } } @@ -216,12 +212,7 @@ public Servlet getServletForPath(String path) { public Enumeration getServlets() { return Collections.enumeration(servletRegistrations.entrySet().stream() .map((e) -> { - try { - return e.getValue().getServlet(); - } catch (ServletException ex) { - ex.printStackTrace(); - return null; - } + return e.getValue().getServlet(); } ) .collect(Collectors.toList())); } 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 index e3ce620bf..c10b6cbfe 100644 --- 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 @@ -1,12 +1,14 @@ package com.amazonaws.serverless.proxy.internal.servlet; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + 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 { +public class AwsServletRegistration implements ServletRegistration, ServletRegistration.Dynamic, Comparable { private String servletName; private Servlet servlet; private AwsServletContext ctx; @@ -91,10 +93,7 @@ public Map getInitParameters() { return initParameters; } - public Servlet getServlet() throws ServletException { - if (servlet.getServletConfig() == null) { - servlet.init(getServletConfig()); - } + public Servlet getServlet() { return servlet; } @@ -127,6 +126,20 @@ public void setAsyncSupported(boolean b) { asyncSupported = b; } + @Override + public int compareTo(AwsServletRegistration r) { + return Integer.compare(loadOnStartup, r.getLoadOnStartup()); + } + + @Override + @SuppressFBWarnings("HE_EQUALS_USE_HASHCODE") + public boolean equals(Object r) { + if (r == null || !AwsServletRegistration.class.isAssignableFrom(r.getClass())) { + return false; + } + return ((AwsServletRegistration)r).getName().equals(getName()) && ((AwsServletRegistration)r).getServlet() == getServlet(); + } + public boolean isAsyncSupported() { return asyncSupported; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainManager.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainManager.java index 8da92f1b3..92ff55daf 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainManager.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainManager.java @@ -14,15 +14,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import javax.servlet.DispatcherType; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.Servlet; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @@ -31,6 +23,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.Predicate; /** * This object in in charge of matching a servlet request to a set of filter, creating the filter chain for a request, @@ -104,12 +97,17 @@ FilterChainHolder getFilterChain(final HttpServletRequest request, Servlet servl return getFilterChainCache(type, targetPath, servlet); } + AwsServletRegistration servletRegistration = (AwsServletRegistration)servletContext.getServletRegistrations() + .values().stream() + .filter((Predicate) servletRegistration1 -> ((AwsServletRegistration) servletRegistration1).getServlet().equals(servlet)) + .findFirst().orElse(null); + FilterChainHolder chainHolder = new FilterChainHolder(); Map registrations = getFilterHolders(); if (registrations == null || registrations.size() == 0) { - if (servlet != null) { - chainHolder.addFilter(new FilterHolder(new ServletExecutionFilter(servlet), servletContext)); + if (servletRegistration != null) { + chainHolder.addFilter(new FilterHolder(new ServletExecutionFilter(servletRegistration), servletContext)); } return chainHolder; } @@ -130,8 +128,8 @@ FilterChainHolder getFilterChain(final HttpServletRequest request, Servlet servl // we assume we only ever have one servlet. } - if (servlet != null) { - chainHolder.addFilter(new FilterHolder(new ServletExecutionFilter(servlet), servletContext)); + if (servletRegistration != null) { + chainHolder.addFilter(new FilterHolder(new ServletExecutionFilter(servletRegistration), servletContext)); } putFilterChainCache(type, targetPath, chainHolder); @@ -332,23 +330,30 @@ void setDispatcherType(DispatcherType dispatcherType) { private class ServletExecutionFilter implements Filter { private FilterConfig config; - private Servlet handlerServlet; + private AwsServletRegistration handlerServlet; + private boolean initialized; - public ServletExecutionFilter(Servlet handler) { - handlerServlet = handler; + public ServletExecutionFilter(AwsServletRegistration servletReg) { + handlerServlet = servletReg; + initialized = handlerServlet.getServlet().getServletInfo() != null; } @Override public void init(FilterConfig filterConfig) throws ServletException { + if (initialized) { + return; + } config = filterConfig; + handlerServlet.getServlet().init(handlerServlet.getServletConfig()); + initialized = true; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - handlerServlet.service(servletRequest, servletResponse); + handlerServlet.getServlet().service(servletRequest, servletResponse); filterChain.doFilter(servletRequest, servletResponse); } 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 893075d2c..987dff14c 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 @@ -83,6 +83,9 @@ public FilterHolder(String name, Filter newFilter, ServletContext context) { * @throws ServletException Propagates any servlet exception thrown by the filter initialization */ public void init() throws ServletException { + if (this.filterInitialized) { + return; + } this.getFilter().init(filterConfig); this.filterInitialized = true; } 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 5168fa96b..dd3f90c93 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 @@ -24,6 +24,7 @@ import org.springframework.web.servlet.DispatcherServlet; import javax.servlet.Servlet; +import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import java.util.concurrent.CountDownLatch; @@ -160,6 +161,9 @@ public void initialize() DispatcherServlet dispatcher = new DispatcherServlet(appContext); ServletRegistration.Dynamic reg = getServletContext().addServlet("dispatcherServlet", dispatcher); reg.addMapping("/"); + reg.setLoadOnStartup(1); + // call initialize on AwsLambdaServletContainerHandler to initialize servlets that are set to load on startup + super.initialize(); Timer.stop("SPRING_COLD_START"); } } 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 c32935c70..6602e9b97 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 @@ -51,11 +51,8 @@ public SpringLambdaContainerHandler springLambdaContainerHandler() throws Contai // servlet name mappings are disabled and will throw an exception //handler.getApplicationInitializer().getDispatcherServlet().setThrowExceptionIfNoHandlerFound(true); - try { - ((DispatcherServlet)((AwsServletRegistration)c.getServletRegistration("dispatcherServlet")).getServlet()).setThrowExceptionIfNoHandlerFound(true); - } catch (ServletException e) { - e.printStackTrace(); - } + ((DispatcherServlet)((AwsServletRegistration)c.getServletRegistration("dispatcherServlet")).getServlet()).setThrowExceptionIfNoHandlerFound(true); + }); return handler; } diff --git a/aws-serverless-java-container-springboot2/pom.xml b/aws-serverless-java-container-springboot2/pom.xml index 316a883b3..2fac94aca 100644 --- a/aws-serverless-java-container-springboot2/pom.xml +++ b/aws-serverless-java-container-springboot2/pom.xml @@ -86,7 +86,6 @@ spring-expression - test 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 e33395360..a3e25fa9b 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 @@ -14,6 +14,7 @@ import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.*; +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.internal.servlet.*; import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; @@ -31,6 +32,7 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; import javax.servlet.Servlet; import javax.servlet.ServletRegistration; @@ -47,6 +49,8 @@ * @param The expected return type */ public class SpringBootLambdaContainerHandler extends AwsLambdaServletContainerHandler { + private static final String DISPATCHER_SERVLET_REGISTRATION_NAME = "dispatcherServlet"; + private final Class springBootInitializer; private static final Logger log = LoggerFactory.getLogger(SpringBootLambdaContainerHandler.class); private String[] springProfiles = null; @@ -169,8 +173,12 @@ public void initialize() applicationContext = builder.run(); if (springWebApplicationType == WebApplicationType.SERVLET) { ((AnnotationConfigServletWebServerApplicationContext)applicationContext).setServletContext(getServletContext()); + AwsServletRegistration reg = (AwsServletRegistration)getServletContext().getServletRegistration(DISPATCHER_SERVLET_REGISTRATION_NAME); + if (reg != null) { + reg.setLoadOnStartup(1); + } } - + super.initialize(); initialized = true; Timer.stop("SPRINGBOOT2_COLD_START"); } From 0967c4200d4dfc657743cb9479368b6a53ee5137 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 31 Mar 2020 10:58:48 -0700 Subject: [PATCH 16/41] feat: Added new 0-parameter constructor for async initializer that uses the actual JVM start time to calculate the timeout milliseconds. Also added the new method to the builder object and deprecated the current method that receives a milliseconds epoch parameter. I'm not deprecating the constructor of the async initializer class that receives the parameter as it may still be useful for tests. This change was suggested in #287 --- .../proxy/AsyncInitializationWrapper.java | 14 ++++++++++++-- .../ServletLambdaContainerHandlerBuilder.java | 18 ++++++++++++++++++ .../springbootslowapp/SBLambdaHandler.java | 2 +- .../spring/springslowapp/LambdaHandler.java | 2 +- .../proxy/spring/slowapp/LambdaHandler.java | 2 +- 5 files changed, 33 insertions(+), 5 deletions(-) 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 index 7ef74fc8b..082769cd7 100644 --- 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 @@ -7,6 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.management.ManagementFactory; import java.time.Instant; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -25,7 +26,7 @@ * seconds has already been used up. */ public class AsyncInitializationWrapper extends InitializationWrapper { - private static final int INIT_GRACE_TIME_MS = 250; + private int INIT_GRACE_TIME_MS = 250; private static final int LAMBDA_MAX_INIT_TIME_MS = 10_000; private CountDownLatch initializationLatch; @@ -41,6 +42,15 @@ public AsyncInitializationWrapper(long startTime) { actualStartTime = startTime; } + /** + * Creates a new instance of the async initializer using the actual JVM start time as the starting point to measure + * the 10 seconds timeout. + */ + public AsyncInitializationWrapper() { + actualStartTime = ManagementFactory.getRuntimeMXBean().getStartTime(); + INIT_GRACE_TIME_MS = 150; + } + @Override public void start(LambdaContainerHandler handler) throws ContainerInitializationException { initializationLatch = new CountDownLatch(1); @@ -50,7 +60,7 @@ public void start(LambdaContainerHandler handler) throws ContainerInitialization 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; + long awaitTime = (actualStartTime + LAMBDA_MAX_INIT_TIME_MS) - curTime - 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 " + 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 index 50743e73f..ff20e200e 100644 --- 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 @@ -132,11 +132,29 @@ public Builder responseTypeClass(Class responseType) { return self(); } + /** + * Uses an async initializer with the given start time to calculate the 10 seconds timeout. + * + * @deprecated As of release 1.5 this method is deprecated in favor of the parameters-less one {@link ServletLambdaContainerHandlerBuilder#asyncInit()}. + * @param actualStartTime An epoch in milliseconds that should be used to calculate the 10 seconds timeout since the start of the application + * @return A builder configured to use the async initializer + */ + @Deprecated public Builder asyncInit(long actualStartTime) { this.initializationWrapper = new AsyncInitializationWrapper(actualStartTime); return self(); } + /** + * Uses a new {@link AsyncInitializationWrapper} with the no-parameter constructor that takes the actual JVM + * start time + * @return A builder configured to use an async initializer + */ + public Builder asyncInit() { + this.initializationWrapper = new AsyncInitializationWrapper(); + 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. 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 index 64a399a22..71e4840a1 100644 --- 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 @@ -22,7 +22,7 @@ public SBLambdaHandler() throws ContainerInitializationException { long startTime = Instant.now().toEpochMilli(); handler = new SpringBootProxyHandlerBuilder() .defaultProxy() - .asyncInit(startTime) + .asyncInit() .springBootApplication(TestApplication.class) .buildAndInitialize(); constructorTime = Instant.now().toEpochMilli() - startTime; 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 index b61a7191f..7eaf54039 100644 --- 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 @@ -18,7 +18,7 @@ public LambdaHandler() throws ContainerInitializationException { long startTime = Instant.now().toEpochMilli(); handler = new SpringProxyHandlerBuilder() .defaultProxy() - .asyncInit(startTime) + .asyncInit() .configurationClasses(SlowAppConfig.class) .buildAndInitialize(); constructorTime = Instant.now().toEpochMilli() - startTime; 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 index bd696a990..cd4d3fa3a 100644 --- 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 @@ -21,7 +21,7 @@ public LambdaHandler() { System.out.println("startCall: " + startTime); handler = new SpringBootProxyHandlerBuilder() .defaultProxy() - .asyncInit(startTime) + .asyncInit() .springBootApplication(SlowTestApplication.class) .buildAndInitialize(); constructorTime = Instant.now().toEpochMilli() - startTime; From 6b915be56f63b1d651465a0659b6ba25cacf458c Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 31 Mar 2020 11:55:40 -0700 Subject: [PATCH 17/41] fix: Updated SpringBoot 1.x handler to use the new servlet initialization mechanism --- .../spring/SpringBootLambdaContainerHandler.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 cb449568c..6f9fe9f60 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,9 +23,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.ConfigurableWebApplicationContext; import javax.servlet.*; @@ -48,6 +50,8 @@ */ @Deprecated public class SpringBootLambdaContainerHandler extends AwsLambdaServletContainerHandler { + private static final String DISPATCHER_SERVLET_REGISTRATION_NAME = "dispatcherServlet"; + private final Class springBootInitializer; private static final Logger log = LoggerFactory.getLogger(SpringBootLambdaContainerHandler.class); private String[] springProfiles = null; @@ -169,8 +173,14 @@ public void initialize() springEnv.setActiveProfiles(springProfiles); app.setEnvironment(springEnv); } - app.run(); + ConfigurableApplicationContext applicationContext = app.run(); + ((ConfigurableWebApplicationContext)applicationContext).setServletContext(getServletContext()); + AwsServletRegistration reg = (AwsServletRegistration)getServletContext().getServletRegistration(DISPATCHER_SERVLET_REGISTRATION_NAME); + if (reg != null) { + reg.setLoadOnStartup(1); + } + super.initialize(); initialized = true; Timer.stop("SPRINGBOOT_COLD_START"); } From bef50122797ce29df1951f3b22d8de3651a4478d Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 31 Mar 2020 12:23:19 -0700 Subject: [PATCH 18/41] ci: switch SpringBoot slow integration test to use a custom async time since the JVM is reused for both tests in the and we cannot reuse the actual JVM init time --- .../proxy/spring/springbootslowapp/SBLambdaHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 71e4840a1..cd1e09bbc 100644 --- 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 @@ -22,7 +22,7 @@ public SBLambdaHandler() throws ContainerInitializationException { long startTime = Instant.now().toEpochMilli(); handler = new SpringBootProxyHandlerBuilder() .defaultProxy() - .asyncInit() + .asyncInit(Instant.now().toEpochMilli()) .springBootApplication(TestApplication.class) .buildAndInitialize(); constructorTime = Instant.now().toEpochMilli() - startTime; From a778710f0e0a844efae74036a84aa7ef75001e86 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 31 Mar 2020 14:00:55 -0700 Subject: [PATCH 19/41] feat: New models for HTTP API support for #329 --- .../proxy/model/HttpApiV2HttpContext.java | 49 +++++ .../proxy/model/HttpApiV2JwtAuthorizer.java | 25 +++ .../proxy/model/HttpApiV2ProxyRequest.java | 109 +++++++++++ .../model/HttpApiV2ProxyRequestContext.java | 172 ++++++++++++++++++ .../model/HttpApiV2ProxyRequestTest.java | 146 +++++++++++++++ 5 files changed, 501 insertions(+) create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2HttpContext.java create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2JwtAuthorizer.java create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequest.java create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2HttpContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2HttpContext.java new file mode 100644 index 000000000..673199d28 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2HttpContext.java @@ -0,0 +1,49 @@ +package com.amazonaws.serverless.proxy.model; + +public class HttpApiV2HttpContext { + private String method; + private String path; + private String protocol; + private String sourceIp; + private String userAgent; + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getSourceIp() { + return sourceIp; + } + + public void setSourceIp(String sourceIp) { + this.sourceIp = sourceIp; + } + + public String getUserAgent() { + return userAgent; + } + + public void setUserAgent(String userAgent) { + this.userAgent = userAgent; + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2JwtAuthorizer.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2JwtAuthorizer.java new file mode 100644 index 000000000..28afa6b05 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2JwtAuthorizer.java @@ -0,0 +1,25 @@ +package com.amazonaws.serverless.proxy.model; + +import java.util.List; +import java.util.Map; + +public class HttpApiV2JwtAuthorizer { + private Map claims; + private List scopes; + + public Map getClaims() { + return claims; + } + + public void setClaims(Map claims) { + this.claims = claims; + } + + public List getScopes() { + return scopes; + } + + public void setScopes(List scopes) { + this.scopes = scopes; + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequest.java new file mode 100644 index 000000000..904f10413 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequest.java @@ -0,0 +1,109 @@ +package com.amazonaws.serverless.proxy.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.Map; + +public class HttpApiV2ProxyRequest { + private String version; + private String routeKey; + private String rawPath; + private String rawQueryString; + private List cookies; + private Map headers; + private Map queryStringParameters; + private String body; + private boolean isBase64Encoded; + private Map stageVariables; + private HttpApiV2ProxyRequestContext requestContext; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getRouteKey() { + return routeKey; + } + + public void setRouteKey(String routeKey) { + this.routeKey = routeKey; + } + + public String getRawPath() { + return rawPath; + } + + public void setRawPath(String rawPath) { + this.rawPath = rawPath; + } + + public String getRawQueryString() { + return rawQueryString; + } + + public void setRawQueryString(String rawQueryString) { + this.rawQueryString = rawQueryString; + } + + public List getCookies() { + return cookies; + } + + public void setCookies(List cookies) { + this.cookies = cookies; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Map getQueryStringParameters() { + return queryStringParameters; + } + + public void setQueryStringParameters(Map queryStringParameters) { + this.queryStringParameters = queryStringParameters; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + @JsonProperty("isBase64Encoded") + public boolean isBase64Encoded() { + return isBase64Encoded; + } + + public void setBase64Encoded(boolean base64Encoded) { + isBase64Encoded = base64Encoded; + } + + public Map getStageVariables() { + return stageVariables; + } + + public void setStageVariables(Map stageVariables) { + this.stageVariables = stageVariables; + } + + public HttpApiV2ProxyRequestContext getRequestContext() { + return requestContext; + } + + public void setRequestContext(HttpApiV2ProxyRequestContext requestContext) { + this.requestContext = requestContext; + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java new file mode 100644 index 000000000..dd0c79fe3 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java @@ -0,0 +1,172 @@ +package com.amazonaws.serverless.proxy.model; + +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; +import java.util.HashMap; + +public class HttpApiV2ProxyRequestContext { + private static final String JWT_KEY = "jwt"; + + private String accountId; + private String apiId; + private String domainName; + private String domainPrefix; + private String requestId; + private String routeKey; + private String stage; + private String time; + private long timeEpoch; + + private HttpApiV2HttpContext http; + private HttpApiV2AuthorizerMap authorizer; + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getApiId() { + return apiId; + } + + public void setApiId(String apiId) { + this.apiId = apiId; + } + + public String getDomainName() { + return domainName; + } + + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + public String getDomainPrefix() { + return domainPrefix; + } + + public void setDomainPrefix(String domainPrefix) { + this.domainPrefix = domainPrefix; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getRouteKey() { + return routeKey; + } + + public void setRouteKey(String routeKey) { + this.routeKey = routeKey; + } + + public String getStage() { + return stage; + } + + public void setStage(String stage) { + this.stage = stage; + } + + public String getTime() { + return time; + } + + public void setTime(String time) { + this.time = time; + } + + public long getTimeEpoch() { + return timeEpoch; + } + + public void setTimeEpoch(long timeEpoch) { + this.timeEpoch = timeEpoch; + } + + public HttpApiV2HttpContext getHttp() { + return http; + } + + public void setHttp(HttpApiV2HttpContext http) { + this.http = http; + } + + public HttpApiV2AuthorizerMap getAuthorizer() { + return authorizer; + } + + public void setAuthorizer(HttpApiV2AuthorizerMap authorizer) { + this.authorizer = authorizer; + } + + @JsonSerialize(using = HttpApiV2AuthorizerSerializer.class) + @JsonDeserialize(using = HttpApiV2AuthorizerDeserializer.class) + public static class HttpApiV2AuthorizerMap extends HashMap { + public HttpApiV2JwtAuthorizer getJwtAuthorizer() { + return (HttpApiV2JwtAuthorizer)get(JWT_KEY); + } + + public boolean isJwt() { + return containsKey(JWT_KEY); + } + + public void putJwtAuthorizer(HttpApiV2JwtAuthorizer jwt) { + put(JWT_KEY, jwt); + } + } + + public static class HttpApiV2AuthorizerDeserializer extends StdDeserializer { + + public HttpApiV2AuthorizerDeserializer() { + super(HttpApiV2AuthorizerMap.class); + } + + @Override + public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + HttpApiV2AuthorizerMap map = new HttpApiV2AuthorizerMap(); + JsonNode node = jsonParser.getCodec().readTree(jsonParser); + if (node.get(JWT_KEY) != null) { + HttpApiV2JwtAuthorizer authorizer = LambdaContainerHandler.getObjectMapper().treeToValue(node.get(JWT_KEY), HttpApiV2JwtAuthorizer.class); + map.putJwtAuthorizer(authorizer); + } + // we ignore other, unknown values + return map; + } + } + + public static class HttpApiV2AuthorizerSerializer extends StdSerializer { + + public HttpApiV2AuthorizerSerializer() { + super(HttpApiV2AuthorizerMap.class); + } + + @Override + public void serialize(HttpApiV2AuthorizerMap httpApiV2AuthorizerMap, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeStartObject(); + if (httpApiV2AuthorizerMap.isJwt()) { + jsonGenerator.writeObjectField(JWT_KEY, httpApiV2AuthorizerMap.getJwtAuthorizer()); + } + jsonGenerator.writeEndObject(); + } + } +} diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java new file mode 100644 index 000000000..4c7be277a --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java @@ -0,0 +1,146 @@ +package com.amazonaws.serverless.proxy.model; + +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.Test; + +import java.util.ArrayList; + +import static org.junit.Assert.*; + +public class HttpApiV2ProxyRequestTest { + + private static final String BASE_PROXY_REQUEST = "{\n" + + " \"version\": \"2.0\",\n" + + " \"routeKey\": \"$default\",\n" + + " \"rawPath\": \"/my/path\",\n" + + " \"rawQueryString\": \"parameter1=value1¶meter1=value2¶meter2=value\",\n" + + " \"cookies\": [ \"cookie1\", \"cookie2\" ],\n" + + " \"headers\": {\n" + + " \"Header1\": \"value1\",\n" + + " \"Header2\": \"value2\"\n" + + " },\n" + + " \"queryStringParameters\": { \"parameter1\": \"value1,value2\", \"parameter2\": \"value\" },\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"123456789012\",\n" + + " \"apiId\": \"api-id\",\n" + + " \"authorizer\": { \"jwt\": {\n" + + " \"claims\": {\"claim1\": \"value1\", \"claim2\": \"value2\"},\n" + + " \"scopes\": [\"scope1\", \"scope2\"]\n" + + " }\n" + + " },\n" + + " \"domainName\": \"id.execute-api.us-east-1.amazonaws.com\",\n" + + " \"domainPrefix\": \"id\",\n" + + " \"http\": {\n" + + " \"method\": \"POST\",\n" + + " \"path\": \"/my/path\",\n" + + " \"protocol\": \"HTTP/1.1\",\n" + + " \"sourceIp\": \"IP\",\n" + + " \"userAgent\": \"agent\"\n" + + " },\n" + + " \"requestId\": \"id\",\n" + + " \"routeKey\": \"$default\",\n" + + " \"stage\": \"$default\",\n" + + " \"time\": \"12/Mar/2020:19:03:58 +0000\",\n" + + " \"timeEpoch\": 1583348638390\n" + + " },\n" + + " \"body\": \"Hello from Lambda\",\n" + + " \"isBase64Encoded\": false,\n" + + " \"stageVariables\": {\"stageVariable1\": \"value1\", \"stageVariable2\": \"value2\"}\n" + + " }\n"; + private static final String NO_AUTH_PROXY = "{\n" + + " \"version\": \"2.0\",\n" + + " \"routeKey\": \"$default\",\n" + + " \"rawPath\": \"/my/path\",\n" + + " \"rawQueryString\": \"parameter1=value1¶meter1=value2¶meter2=value\",\n" + + " \"cookies\": [ \"cookie1\", \"cookie2\" ],\n" + + " \"headers\": {\n" + + " \"Header1\": \"value1\",\n" + + " \"Header2\": \"value2\"\n" + + " },\n" + + " \"queryStringParameters\": { \"parameter1\": \"value1,value2\", \"parameter2\": \"value\" },\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"123456789012\",\n" + + " \"apiId\": \"api-id\",\n" + + " \"authorizer\": {\n " + + " },\n" + + " \"domainName\": \"id.execute-api.us-east-1.amazonaws.com\",\n" + + " \"domainPrefix\": \"id\",\n" + + " \"http\": {\n" + + " \"method\": \"POST\",\n" + + " \"path\": \"/my/path\",\n" + + " \"protocol\": \"HTTP/1.1\",\n" + + " \"sourceIp\": \"IP\",\n" + + " \"userAgent\": \"agent\"\n" + + " },\n" + + " \"requestId\": \"id\",\n" + + " \"routeKey\": \"$default\",\n" + + " \"stage\": \"$default\",\n" + + " \"time\": \"12/Mar/2020:19:03:58 +0000\",\n" + + " \"timeEpoch\": 1583348638390\n" + + " },\n" + + " \"body\": \"Hello from Lambda\",\n" + + " \"isBase64Encoded\": true,\n" + + " \"stageVariables\": {\"stageVariable1\": \"value1\", \"stageVariable2\": \"value2\"}\n" + + " }\n"; + + @Test + public void deserialize_fromJsonString_authorizerPopulatedCorrectly() { + try { + HttpApiV2ProxyRequest req = LambdaContainerHandler.getObjectMapper().readValue(BASE_PROXY_REQUEST, HttpApiV2ProxyRequest.class); + assertTrue(req.getRequestContext().getAuthorizer().getJwtAuthorizer().getClaims().containsKey("claim1")); + assertEquals(2, req.getRequestContext().getAuthorizer().getJwtAuthorizer().getScopes().size()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + fail("Exception while parsing request" + e.getMessage()); + } + } + + @Test + public void deserialize_fromJsonString_authorizerEmptyMap() { + try { + HttpApiV2ProxyRequest req = LambdaContainerHandler.getObjectMapper().readValue(NO_AUTH_PROXY, HttpApiV2ProxyRequest.class); + assertNotNull(req.getRequestContext().getAuthorizer()); + assertFalse(req.getRequestContext().getAuthorizer().isJwt()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + fail("Exception while parsing request" + e.getMessage()); + } + } + + @Test + public void deserialize_fromJsonString_isBase64EncodedPopulates() { + try { + HttpApiV2ProxyRequest req = LambdaContainerHandler.getObjectMapper().readValue(BASE_PROXY_REQUEST, HttpApiV2ProxyRequest.class); + assertFalse(req.isBase64Encoded()); + req = LambdaContainerHandler.getObjectMapper().readValue(NO_AUTH_PROXY, HttpApiV2ProxyRequest.class); + assertTrue(req.isBase64Encoded()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + fail("Exception while parsing request" + e.getMessage()); + } + } + + @Test + public void serialize_toJsonString_authorizerPopulatesCorrectly() { + HttpApiV2ProxyRequest req = new HttpApiV2ProxyRequest(); + req.setBase64Encoded(false); + req.setRequestContext(new HttpApiV2ProxyRequestContext()); + req.getRequestContext().setAuthorizer(new HttpApiV2ProxyRequestContext.HttpApiV2AuthorizerMap()); + req.getRequestContext().getAuthorizer().putJwtAuthorizer(new HttpApiV2JwtAuthorizer()); + ArrayList scopes = new ArrayList<>(); + scopes.add("first"); + scopes.add("second"); + req.getRequestContext().getAuthorizer().getJwtAuthorizer().setScopes(scopes); + + try { + String reqString = LambdaContainerHandler.getObjectMapper().writeValueAsString(req); + assertTrue(reqString.contains("\"scopes\":[\"first\",\"second\"]")); + assertTrue(reqString.contains("\"authorizer\":{\"jwt\":{")); + assertTrue(reqString.contains("\"isBase64Encoded\":false")); + } catch (JsonProcessingException e) { + e.printStackTrace(); + fail("Exception while serializing request" + e.getMessage()); + } + } +} From 80c5769cc82dfbb4244b49da21852f2b92d620d1 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 31 Mar 2020 21:39:34 -0700 Subject: [PATCH 20/41] feat: First implementation of HTTP API servlet request, request reader, and security context writer - continuing to address #329 --- .../AwsHttpApiV2SecurityContextWriter.java | 14 + .../serverless/proxy/RequestReader.java | 13 + .../jaxrs/AwsHttpApiV2SecurityContext.java | 90 ++++ .../AwsHttpApiV2HttpServletRequestReader.java | 37 ++ .../AwsHttpApiV2ProxyHttpServletRequest.java | 500 ++++++++++++++++++ .../servlet/AwsHttpServletRequest.java | 340 +++++++++++- .../servlet/AwsProxyHttpServletRequest.java | 306 +---------- .../AwsProxyHttpServletRequestReader.java | 1 + .../proxy/model/HttpApiV2AuthorizerMap.java | 72 +++ .../model/HttpApiV2ProxyRequestContext.java | 67 --- 10 files changed, 1065 insertions(+), 375 deletions(-) create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsHttpApiV2SecurityContextWriter.java create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsHttpApiV2SecurityContext.java create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsHttpApiV2SecurityContextWriter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsHttpApiV2SecurityContextWriter.java new file mode 100644 index 000000000..a90c420d2 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsHttpApiV2SecurityContextWriter.java @@ -0,0 +1,14 @@ +package com.amazonaws.serverless.proxy; + +import com.amazonaws.serverless.proxy.internal.jaxrs.AwsHttpApiV2SecurityContext; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; +import com.amazonaws.services.lambda.runtime.Context; + +import javax.ws.rs.core.SecurityContext; + +public class AwsHttpApiV2SecurityContextWriter implements SecurityContextWriter { + @Override + public SecurityContext writeSecurityContext(HttpApiV2ProxyRequest event, Context lambdaContext) { + return new AwsHttpApiV2SecurityContext(lambdaContext, event); + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/RequestReader.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/RequestReader.java index 096d49ca5..e0203d76b 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/RequestReader.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/RequestReader.java @@ -65,7 +65,20 @@ public abstract class RequestReader { */ public static final String JAX_SECURITY_CONTEXT_PROPERTY = "com.amazonaws.serverless.jaxrs.securityContext"; + /** + * The key for the HTTP API request context passed by the services + */ + public static final String HTTP_API_CONTEXT_PROPERTY = "com.amazonaws.httpapi.request.context"; + /** + * The key for the HTTP API stage variables + */ + public static final String HTTP_API_STAGE_VARS_PROPERTY = "com.amazonaws.httpapi.stage.variables"; + + /** + * The key for the HTTP API proxy request event + */ + public static final String HTTP_API_EVENT_PROPERTY = "com.amazonaws.httpapi.request"; //------------------------------------------------------------- // Methods - Abstract diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsHttpApiV2SecurityContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsHttpApiV2SecurityContext.java new file mode 100644 index 000000000..a54fd7fa5 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsHttpApiV2SecurityContext.java @@ -0,0 +1,90 @@ +package com.amazonaws.serverless.proxy.internal.jaxrs; + +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.SecurityUtils; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.SecurityContext; +import java.nio.charset.StandardCharsets; +import java.security.Principal; +import java.util.Base64; + +public class AwsHttpApiV2SecurityContext implements SecurityContext { + public static final String AUTH_SCHEME_JWT = "JWT"; + + private static Logger log = LoggerFactory.getLogger(AwsHttpApiV2SecurityContext.class); + + private Context lambdaContext; + private HttpApiV2ProxyRequest event; + + public AwsHttpApiV2SecurityContext(final Context lambdaCtx, final HttpApiV2ProxyRequest request) { + lambdaContext = lambdaCtx; + event = request; + } + + @Override + public Principal getUserPrincipal() { + if (getAuthenticationScheme() == null || !event.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) { + return null; + } + + String authValue = event.getHeaders().get(HttpHeaders.AUTHORIZATION); + if (authValue.startsWith("Bearer ")) { + authValue = authValue.replace("Bearer ", ""); + } + String[] parts = authValue.split("\\."); + if (parts.length != 3) { + log.warn("Could not parse JWT token for requestId: " + SecurityUtils.crlf(event.getRequestContext().getRequestId())); + return null; + } + String decodedBody = new String(Base64.getMimeDecoder().decode(parts[1]), StandardCharsets.UTF_8); + try { + JsonNode parsedBody = LambdaContainerHandler.getObjectMapper().readTree(decodedBody); + if (!parsedBody.isObject() && parsedBody.has("sub")) { + log.debug("Could not find \"sub\" field in JWT body for requestId: " + SecurityUtils.crlf(event.getRequestContext().getRequestId())); + return null; + } + String subject = parsedBody.get("sub").asText(); + return (() -> { + return subject; + }); + } catch (JsonProcessingException e) { + log.error("Error while attempting to parse JWT body for requestId: " + SecurityUtils.crlf(event.getRequestContext().getRequestId()), e); + return null; + } + + } + + @Override + public boolean isUserInRole(String s) { + if (getAuthenticationScheme() == null) { + return false; + } + + return event.getRequestContext().getAuthorizer().getJwtAuthorizer().getScopes().contains(s) || + event.getRequestContext().getAuthorizer().getJwtAuthorizer().getClaims().containsKey(s); + + } + + @Override + public boolean isSecure() { + return getAuthenticationScheme() != null; + } + + @Override + public String getAuthenticationScheme() { + if (event.getRequestContext().getAuthorizer() == null) { + return null; + } + if (event.getRequestContext().getAuthorizer().isJwt()) { + return AUTH_SCHEME_JWT; + } + return null; + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java new file mode 100644 index 000000000..bb4528023 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java @@ -0,0 +1,37 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import com.amazonaws.serverless.exceptions.InvalidRequestEventException; +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.model.ContainerConfig; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; +import com.amazonaws.services.lambda.runtime.Context; + +import javax.ws.rs.core.SecurityContext; + +public class AwsHttpApiV2HttpServletRequestReader extends RequestReader { + static final String INVALID_REQUEST_ERROR = "The incoming event is not a valid HTTP API v2 proxy request"; + + @Override + public AwsHttpApiV2ProxyHttpServletRequest readRequest(HttpApiV2ProxyRequest request, SecurityContext securityContext, Context lambdaContext, ContainerConfig config) throws InvalidRequestEventException { + if (request.getRequestContext() == null || request.getRequestContext().getHttp().getMethod() == null || request.getRequestContext().getHttp().getMethod().equals("")) { + throw new InvalidRequestEventException(INVALID_REQUEST_ERROR); + } + + // clean out the request path based on the container config + request.setRawPath(stripBasePath(request.getRawPath(), config)); + + AwsHttpApiV2ProxyHttpServletRequest servletRequest = new AwsHttpApiV2ProxyHttpServletRequest(request, lambdaContext, securityContext, config); + servletRequest.setAttribute(HTTP_API_CONTEXT_PROPERTY, request.getRequestContext()); + servletRequest.setAttribute(HTTP_API_STAGE_VARS_PROPERTY, request.getStageVariables()); + servletRequest.setAttribute(HTTP_API_EVENT_PROPERTY, request); + servletRequest.setAttribute(LAMBDA_CONTEXT_PROPERTY, lambdaContext); + servletRequest.setAttribute(JAX_SECURITY_CONTEXT_PROPERTY, securityContext); + + return servletRequest; + } + + @Override + protected Class getRequestClass() { + return HttpApiV2ProxyRequest.class; + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java new file mode 100644 index 000000000..c4148c1bc --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java @@ -0,0 +1,500 @@ +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.ContainerConfig; +import com.amazonaws.serverless.proxy.model.Headers; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; +import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; +import com.amazonaws.services.lambda.runtime.Context; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.*; +import javax.servlet.http.*; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.SecurityContext; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.security.Principal; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.util.*; + +public class AwsHttpApiV2ProxyHttpServletRequest extends AwsHttpServletRequest { + private static Logger log = LoggerFactory.getLogger(AwsHttpApiV2ProxyHttpServletRequest.class); + + private HttpApiV2ProxyRequest request; + private MultiValuedTreeMap queryString; + private Headers headers; + private ContainerConfig config; + private SecurityContext securityContext; + private AwsAsyncContext asyncContext; + + /** + * Protected constructors for implementing classes. This should be called first with the context received from + * AWS Lambda + * + * @param lambdaContext The Lambda function context. This object is used for utility methods such as log + */ + public AwsHttpApiV2ProxyHttpServletRequest(HttpApiV2ProxyRequest req, Context lambdaContext, SecurityContext sc, ContainerConfig cfg) { + super(lambdaContext); + request = req; + config = cfg; + securityContext = sc; + queryString = parseRawQueryString(request.getRawQueryString()); + headers = headersMapToMultiValue(request.getHeaders()); + } + + @Override + public String getAuthType() { + // TODO + return null; + } + + @Override + public Cookie[] getCookies() { + if (headers == null || !headers.containsKey(HttpHeaders.COOKIE)) { + return new Cookie[0]; + } + + return parseCookieHeaderValue(headers.getFirst(HttpHeaders.COOKIE)); + } + + @Override + public long getDateHeader(String s) { + if (headers == null) { + return -1L; + } + String dateString = headers.getFirst(s); + if (dateString == null) { + return -1L; + } + try { + return Instant.from(ZonedDateTime.parse(dateString, dateFormatter)).toEpochMilli(); + } catch (DateTimeParseException e) { + log.warn("Invalid date header in request: " + SecurityUtils.crlf(dateString)); + return -1L; + } + } + + @Override + public String getHeader(String s) { + if (headers == null) { + return null; + } + return headers.getFirst(s); + } + + @Override + public Enumeration getHeaders(String s) { + if (headers == null || !headers.containsKey(s)) { + return Collections.emptyEnumeration(); + } + return Collections.enumeration(headers.get(s)); + } + + @Override + public Enumeration getHeaderNames() { + if (headers == null) { + return Collections.emptyEnumeration(); + } + return Collections.enumeration(headers.keySet()); + } + + @Override + public int getIntHeader(String s) { + if (headers == null) { + return -1; + } + String headerValue = headers.getFirst(s); + if (headerValue == null || "".equals(headerValue)) { + return -1; + } + + return Integer.parseInt(headerValue); + } + + @Override + public String getMethod() { + return request.getRequestContext().getHttp().getMethod(); + } + + @Override + public String getPathInfo() { + String pathInfo = cleanUri(request.getRawPath()); + return decodeRequestPath(pathInfo, LambdaContainerHandler.getContainerConfig()); + } + + @Override + public String getPathTranslated() { + // Return null because it is an archive on a remote system + return null; + } + + @Override + public String getContextPath() { + return generateContextPath(config, request.getRequestContext().getStage()); + } + + @Override + public String getQueryString() { + return request.getRawQueryString(); + } + + @Override + public String getRemoteUser() { + if (securityContext == null || securityContext.getUserPrincipal() == null) { + return null; + } + return securityContext.getUserPrincipal().getName(); + } + + @Override + public boolean isUserInRole(String s) { + // TODO: Not supported + return false; + } + + @Override + public Principal getUserPrincipal() { + if (securityContext == null) { + return null; + } + return securityContext.getUserPrincipal(); + } + + @Override + public String getRequestURI() { + return cleanUri(getContextPath()) + cleanUri(request.getRawPath()); + } + + @Override + public StringBuffer getRequestURL() { + return generateRequestURL(request.getRawPath()); + } + + + @Override + public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { + throw new UnsupportedOperationException(); + } + + @Override + public void login(String s, String s1) throws ServletException { + throw new UnsupportedOperationException(); + } + + @Override + public void logout() throws ServletException { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getParts() throws IOException, ServletException { + return getMultipartFormParametersMap().values(); + } + + @Override + public Part getPart(String s) throws IOException, ServletException { + return getMultipartFormParametersMap().get(s); + } + + @Override + public T upgrade(Class aClass) throws IOException, ServletException { + throw new UnsupportedOperationException(); + } + + @Override + public String getCharacterEncoding() { + if (headers == null) { + return null; + } + return parseCharacterEncoding(headers.getFirst(HttpHeaders.CONTENT_TYPE)); + } + + @Override + public void setCharacterEncoding(String s) throws UnsupportedEncodingException { + if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) { + log.debug("Called set character encoding to " + SecurityUtils.crlf(s) + " on a request without a content type. Character encoding will not be set"); + return; + } + String currentContentType = headers.getFirst(HttpHeaders.CONTENT_TYPE); + headers.putSingle(HttpHeaders.CONTENT_TYPE, appendCharacterEncoding(currentContentType, s)); + } + + @Override + public int getContentLength() { + String headerValue = headers.getFirst(HttpHeaders.CONTENT_LENGTH); + if (headerValue == null) { + return -1; + } + return Integer.parseInt(headerValue); + } + + @Override + public long getContentLengthLong() { + String headerValue = headers.getFirst(HttpHeaders.CONTENT_LENGTH); + if (headerValue == null) { + return -1; + } + return Long.parseLong(headerValue); + } + + @Override + public String getContentType() { + if (headers == null) { + return null; + } + return headers.getFirst(HttpHeaders.CONTENT_TYPE); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return bodyStringToInputStream(request.getBody(), request.isBase64Encoded()); + } + + @Override + public String getParameter(String s) { + String queryStringParameter = getFirstQueryParamValue(queryString, s, config.isQueryStringCaseSensitive()); + if (queryStringParameter != null) { + return queryStringParameter; + } + + String[] bodyParams = getFormBodyParameterCaseInsensitive(s); + if (bodyParams.length == 0) { + return null; + } else { + return bodyParams[0]; + } + } + + @Override + public Enumeration getParameterNames() { + if (queryString == null) { + return Collections.emptyEnumeration(); + } + + return Collections.enumeration(queryString.keySet()); + } + + @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<>(Arrays.asList(getQueryParamValues(queryString, s, config.isQueryStringCaseSensitive()))); + + values.addAll(Arrays.asList(getFormBodyParameterCaseInsensitive(s))); + + if (values.size() == 0) { + return null; + } else { + return values.toArray(new String[0]); + } + } + + @Override + public Map getParameterMap() { + return generateParameterMap(queryString, config); + } + + @Override + public String getProtocol() { + return request.getRequestContext().getHttp().getProtocol(); + } + + @Override + public String getScheme() { + return getSchemeFromHeader(headers); + } + + @Override + public String getServerName() { + // we match the behavior of the v1 proxy request here. Should we? + String region = System.getenv("AWS_REGION"); + if (region == null) { + // this is not a critical failure, we just put a static region in the URI + region = "us-east-1"; + } + + if (headers != null && headers.containsKey(HOST_HEADER_NAME)) { + String hostHeader = headers.getFirst(HOST_HEADER_NAME); + if (SecurityUtils.isValidHost(hostHeader, request.getRequestContext().getApiId(), region)) { + return hostHeader; + } + } + + return request.getRequestContext().getDomainName(); + } + + @Override + public int getServerPort() { + if (headers == null || !headers.containsKey(PORT_HEADER_NAME)) { + return 443; // we default to 443 as HTTP APIs can only be HTTPS + } + String port = headers.getFirst(PORT_HEADER_NAME); + if (SecurityUtils.isValidPort(port)) { + return Integer.parseInt(port); + } + return 443; // default port + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new StringReader(request.getBody())); + } + + @Override + public String getRemoteAddr() { + if (request.getRequestContext() == null || request.getRequestContext().getHttp() == null || request.getRequestContext().getHttp().getSourceIp() == null) { + return "127.0.0.1"; + } + return request.getRequestContext().getHttp().getSourceIp(); + } + + @Override + public String getRemoteHost() { + if (headers == null) { + return null; + } + return headers.getFirst(HttpHeaders.HOST); + } + + @Override + public Locale getLocale() { + // Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5 + List values = this.parseHeaderValue( + headers.getFirst(HttpHeaders.ACCEPT_LANGUAGE), ",", ";" + ); + if (values.size() == 0) { + return Locale.getDefault(); + } + return new Locale(values.get(0).getValue()); + } + + @Override + public Enumeration getLocales() { + List values = this.parseHeaderValue( + headers.getFirst(HttpHeaders.ACCEPT_LANGUAGE), ",", ";" + ); + + List locales = new ArrayList<>(); + if (values.size() == 0) { + locales.add(Locale.getDefault()); + } else { + for (HeaderValue locale : values) { + locales.add(new Locale(locale.getValue())); + } + } + + return Collections.enumeration(locales); + } + + @Override + public boolean isSecure() { + return securityContext.isSecure(); + } + + @Override + public RequestDispatcher getRequestDispatcher(String s) { + return getServletContext().getRequestDispatcher(s); + } + + @Override + public String getRealPath(String s) { + // we are in an archive on a remote server + return null; + } + + @Override + public int getRemotePort() { + return 0; + } + + @Override + public boolean isAsyncSupported() { + return true; + } + + @Override + public boolean isAsyncStarted() { + if (asyncContext == null) { + return false; + } + if (asyncContext.isCompleted() || asyncContext.isDispatched()) { + return false; + } + return true; + } + + @Override + 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; + } + + @Override + 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; + } + + @Override + public AsyncContext getAsyncContext() { + if (asyncContext == null) { + throw new IllegalStateException("Request " + SecurityUtils.crlf(request.getRequestContext().getRequestId()) + + " is not in asynchronous mode. Call startAsync before attempting to get the async context."); + } + return asyncContext; + } + + private MultiValuedTreeMap parseRawQueryString(String qs) { + if (qs == null || "".equals(qs.trim())) { + return new MultiValuedTreeMap<>(); + } + + MultiValuedTreeMap qsMap = new MultiValuedTreeMap<>(); + for (String value : qs.split(QUERY_STRING_SEPARATOR)) { + if (!value.contains(QUERY_STRING_KEY_VALUE_SEPARATOR)) { + log.warn("Invalid query string parameter: " + SecurityUtils.crlf(value)); + continue; + } + + String[] kv = value.split(QUERY_STRING_KEY_VALUE_SEPARATOR); + try { + qsMap.add(URLDecoder.decode(kv[0], LambdaContainerHandler.getContainerConfig().getUriEncoding()), kv[1]); + } catch (UnsupportedEncodingException e) { + log.error("Unsupported encoding in query string key: " + SecurityUtils.crlf(kv[0]), e); + } + } + return qsMap; + } + + private Headers headersMapToMultiValue(Map headers) { + if (headers == null || headers.size() == 0) { + return new Headers(); + } + + Headers h = new Headers(); + for (Map.Entry hkv : headers.entrySet()) { + // Exceptions for known header values that contain commas + if (hkv.getKey().equalsIgnoreCase(HttpHeaders.DATE) || hkv.getKey().toLowerCase(Locale.getDefault()).startsWith("accept-")) { + h.add(hkv.getKey(), hkv.getValue()); + continue; + } + + for (String value : hkv.getValue().split(",")) { + h.add(hkv.getKey(), value); + } + } + return h; + } +} 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 35773cee9..f88bdcaf9 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 @@ -13,32 +13,36 @@ package com.amazonaws.serverless.proxy.internal.servlet; import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.internal.SecurityUtils; -import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; -import com.amazonaws.serverless.proxy.model.ContainerConfig; -import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; +import com.amazonaws.serverless.proxy.model.*; import com.amazonaws.services.lambda.runtime.Context; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.NullInputStream; import org.apache.http.message.BasicHeaderValueParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.*; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import javax.servlet.http.*; +import javax.ws.rs.core.MediaType; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** @@ -59,6 +63,8 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { 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"; + static final String QUERY_STRING_SEPARATOR = "&"; + static final String QUERY_STRING_KEY_VALUE_SEPARATOR = "="; // We need this to pickup the protocol from the CloudFront header since Lambda doesn't receive this // information from anywhere else @@ -78,6 +84,12 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { private AwsHttpSession session; private String queryString; private BasicHeaderValueParser headerParser; + private Map multipartFormParameters; + private Map> urlEncodedFormParameters; + + protected AwsHttpServletResponse response; + protected AwsLambdaServletContainerHandler containerHandler; + private static Logger log = LoggerFactory.getLogger(AwsHttpServletRequest.class); @@ -98,6 +110,18 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.REQUEST); } + public AwsHttpServletResponse getResponse() { + return response; + } + + public void setResponse(AwsHttpServletResponse response) { + this.response = response; + } + + public void setContainerHandler(AwsLambdaServletContainerHandler containerHandler) { + this.containerHandler = containerHandler; + } + //------------------------------------------------------------- // Implementation - HttpServletRequest @@ -247,6 +271,12 @@ public DispatcherType getDispatcherType() { return DispatcherType.REQUEST; } + @Override + public String getServletPath() { + // we always work on the root path + return ""; + } + //------------------------------------------------------------- // Methods - Getter/Setter @@ -323,6 +353,257 @@ protected String generateQueryString(MultiValuedTreeMap paramete return queryString; } + protected String generateContextPath(ContainerConfig config, String apiStage) { + String contextPath = ""; + if (config.isUseStageAsServletContext() && apiStage != null) { + log.debug("Using stage as context path"); + contextPath = cleanUri(apiStage); + } + if (config.getServiceBasePath() != null) { + contextPath += cleanUri(config.getServiceBasePath()); + } + + return contextPath; + } + + protected StringBuffer generateRequestURL(String requestPath) { + String url = ""; + url += getServerName(); + url += cleanUri(getContextPath()); + url += cleanUri(requestPath); + + return new StringBuffer(getScheme() + "://" + url); + } + + protected String parseCharacterEncoding(String contentTypeHeader) { + // we only look at content-type because content-encoding should only be used for + // "binary" requests such as gzip/deflate. + if (contentTypeHeader == null) { + return null; + } + + String[] contentTypeValues = contentTypeHeader.split(HEADER_VALUE_SEPARATOR); + if (contentTypeValues.length <= 1) { + return null; + } + + for (String contentTypeValue : contentTypeValues) { + if (contentTypeValue.trim().startsWith(ENCODING_VALUE_KEY)) { + String[] encodingValues = contentTypeValue.split(HEADER_KEY_VALUE_SEPARATOR); + if (encodingValues.length <= 1) { + return null; + } + return encodingValues[1]; + } + } + return null; + } + + protected String appendCharacterEncoding(String currentContentType, String newEncoding) { + if (currentContentType == null || "".equals(currentContentType.trim())) { + return null; + } + + if (currentContentType.contains(HEADER_VALUE_SEPARATOR)) { + String[] contentTypeValues = currentContentType.split(HEADER_VALUE_SEPARATOR); + StringBuilder contentType = new StringBuilder(contentTypeValues[0]); + + for (int i = 1; i < contentTypeValues.length; i++) { + String contentTypeValue = contentTypeValues[i]; + String contentTypeString = HEADER_VALUE_SEPARATOR + " " + contentTypeValue; + if (contentTypeValue.trim().startsWith(ENCODING_VALUE_KEY)) { + contentTypeString = HEADER_VALUE_SEPARATOR + " " + ENCODING_VALUE_KEY + HEADER_KEY_VALUE_SEPARATOR + newEncoding; + } + contentType.append(contentTypeString); + } + + return contentType.toString(); + } else { + return currentContentType + HEADER_VALUE_SEPARATOR + " " + ENCODING_VALUE_KEY + HEADER_KEY_VALUE_SEPARATOR + newEncoding; + } + } + + protected ServletInputStream bodyStringToInputStream(String body, boolean isBase64Encoded) throws IOException { + if (body == null) { + return new AwsProxyHttpServletRequest.AwsServletInputStream(new NullInputStream(0, false, false)); + } + byte[] bodyBytes; + if (isBase64Encoded) { + bodyBytes = Base64.getMimeDecoder().decode(body); + } else { + String encoding = getCharacterEncoding(); + if (encoding == null) { + encoding = StandardCharsets.ISO_8859_1.name(); + } + try { + bodyBytes = body.getBytes(encoding); + } catch (Exception e) { + log.error("Could not read request with character encoding: " + SecurityUtils.crlf(encoding), e); + bodyBytes = body.getBytes(StandardCharsets.ISO_8859_1.name()); + } + } + ByteArrayInputStream requestBodyStream = new ByteArrayInputStream(bodyBytes); + return new AwsProxyHttpServletRequest.AwsServletInputStream(requestBodyStream); + } + + protected String getFirstQueryParamValue(MultiValuedTreeMap queryString, String key, boolean isCaseSensitive) { + if (queryString != null) { + if (isCaseSensitive) { + return queryString.getFirst(key); + } + + for (String k : queryString.keySet()) { + if (k.toLowerCase(Locale.getDefault()).equals(key.toLowerCase(Locale.getDefault()))) { + return queryString.getFirst(k); + } + } + } + + return null; + } + + protected String[] getFormBodyParameterCaseInsensitive(String key) { + List values = getFormUrlEncodedParametersMap().get(key); + if (values != null) { + String[] valuesArray = new String[values.size()]; + valuesArray = values.toArray(valuesArray); + return valuesArray; + } else { + return new String[0]; + } + } + + + protected Map> getFormUrlEncodedParametersMap() { + if (urlEncodedFormParameters != null) { + return urlEncodedFormParameters; + } + String contentType = getContentType(); + if (contentType == null) { + urlEncodedFormParameters = new HashMap<>(); + return urlEncodedFormParameters; + } + if (!contentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED) || !getMethod().toLowerCase(Locale.ENGLISH).equals("post")) { + urlEncodedFormParameters = new HashMap<>(); + return urlEncodedFormParameters; + } + Timer.start("SERVLET_REQUEST_GET_FORM_PARAMS"); + String rawBodyContent = null; + try { + rawBodyContent = IOUtils.toString(getInputStream()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + urlEncodedFormParameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (String parameter : rawBodyContent.split(FORM_DATA_SEPARATOR)) { + String[] parameterKeyValue = parameter.split(HEADER_KEY_VALUE_SEPARATOR); + if (parameterKeyValue.length < 2) { + continue; + } + List values = new ArrayList<>(); + if (urlEncodedFormParameters.containsKey(parameterKeyValue[0])) { + values = urlEncodedFormParameters.get(parameterKeyValue[0]); + } + values.add(decodeValueIfEncoded(parameterKeyValue[1])); + urlEncodedFormParameters.put(decodeValueIfEncoded(parameterKeyValue[0]), values); + } + Timer.stop("SERVLET_REQUEST_GET_FORM_PARAMS"); + return urlEncodedFormParameters; + } + + @SuppressFBWarnings({"FILE_UPLOAD_FILENAME", "WEAK_FILENAMEUTILS"}) + protected Map getMultipartFormParametersMap() { + if (multipartFormParameters != null) { + return multipartFormParameters; + } + if (!ServletFileUpload.isMultipartContent(this)) { // isMultipartContent also checks the content type + multipartFormParameters = new HashMap<>(); + return multipartFormParameters; + } + Timer.start("SERVLET_REQUEST_GET_MULTIPART_PARAMS"); + multipartFormParameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); + + try { + List items = upload.parseRequest(this); + for (FileItem item : items) { + String fileName = FilenameUtils.getName(item.getName()); + AwsProxyRequestPart newPart = new AwsProxyRequestPart(item.get()); + newPart.setName(item.getFieldName()); + newPart.setSubmittedFileName(fileName); + newPart.setContentType(item.getContentType()); + newPart.setSize(item.getSize()); + item.getHeaders().getHeaderNames().forEachRemaining(h -> { + newPart.addHeader(h, item.getHeaders().getHeader(h)); + }); + + multipartFormParameters.put(item.getFieldName(), newPart); + } + } catch (FileUploadException e) { + Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS"); + log.error("Could not read multipart upload file", e); + } + Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS"); + return multipartFormParameters; + } + + protected String[] getQueryParamValues(MultiValuedTreeMap qs, String key, boolean isCaseSensitive) { + if (qs != null) { + if (isCaseSensitive) { + return qs.get(key).toArray(new String[0]); + } + + for (String k : qs.keySet()) { + if (k.toLowerCase(Locale.getDefault()).equals(key.toLowerCase(Locale.getDefault()))) { + return qs.get(k).toArray(new String[0]); + } + } + } + + return new String[0]; + } + + protected Map generateParameterMap(MultiValuedTreeMap qs, ContainerConfig config) { + Map output = new HashMap<>(); + + Map> params = getFormUrlEncodedParametersMap(); + params.entrySet().stream().parallel().forEach(e -> { + output.put(e.getKey(), e.getValue().toArray(new String[0])); + }); + + if (qs != null) { + qs.keySet().stream().parallel().forEach(e -> { + List newValues = new ArrayList<>(); + if (output.containsKey(e)) { + String[] values = output.get(e); + newValues.addAll(Arrays.asList(values)); + } + newValues.addAll(Arrays.asList(getQueryParamValues(qs, e, config.isQueryStringCaseSensitive()))); + output.put(e, newValues.toArray(new String[0])); + }); + } + + return output; + } + + protected String getSchemeFromHeader(Headers headers) { + // if we don't have any headers to deduce the value we assume HTTPS - API Gateway's default + if (headers == null) { + return "https"; + } + String cfScheme = headers.getFirst(CF_PROTOCOL_HEADER_NAME); + if (cfScheme != null && SecurityUtils.isValidScheme(cfScheme)) { + return cfScheme; + } + String gwScheme = headers.getFirst(PROTOCOL_HEADER_NAME); + if (gwScheme != null && SecurityUtils.isValidScheme(gwScheme)) { + return gwScheme; + } + // https is our default scheme + return "https"; + } /** * Prases a header value using the default value separator "," and qualifier separator ";". @@ -426,6 +707,39 @@ static String decodeRequestPath(String requestPath, ContainerConfig config) { } + static String cleanUri(String uri) { + String finalUri = (uri == null ? "/" : uri); + if (finalUri.equals("/")) { + return finalUri; + } + + if (!finalUri.startsWith("/")) { + finalUri = "/" + finalUri; + } + + if (finalUri.endsWith("/")) { + finalUri = finalUri.substring(0, finalUri.length() - 1); + } + + finalUri = finalUri.replaceAll("/+", "/"); + + return finalUri; + } + + static String decodeValueIfEncoded(String value) { + if (value == null) { + return null; + } + + try { + return URLDecoder.decode(value, LambdaContainerHandler.getContainerConfig().getUriEncoding()); + } catch (UnsupportedEncodingException e) { + log.warn("Could not decode body content - proceeding as if it was already decoded", e); + return value; + } + } + + /** * Class that represents a header value. */ 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 4c69b535e..13f8ef7ef 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 @@ -77,12 +77,8 @@ public class AwsProxyHttpServletRequest extends AwsHttpServletRequest { private AwsProxyRequest request; private SecurityContext securityContext; private AwsAsyncContext 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 @@ -101,23 +97,10 @@ public AwsProxyHttpServletRequest(AwsProxyRequest awsProxyRequest, Context lambd this.config = config; } - public AwsProxyRequest getAwsProxyRequest() { return this.request; } - public AwsHttpServletResponse getResponse() { - return response; - } - - public void setResponse(AwsHttpServletResponse response) { - this.response = response; - } - - public void setContainerHandler(AwsLambdaServletContainerHandler containerHandler) { - this.containerHandler = containerHandler; - } - //------------------------------------------------------------- // Implementation - HttpServletRequest //------------------------------------------------------------- @@ -224,16 +207,7 @@ public String getPathTranslated() { @Override public String getContextPath() { - String contextPath = ""; - if (config.isUseStageAsServletContext() && request.getRequestContext().getStage() != null) { - log.debug("Using stage as context path"); - contextPath = cleanUri(request.getRequestContext().getStage()); - } - if (config.getServiceBasePath() != null) { - contextPath += cleanUri(config.getServiceBasePath()); - } - - return contextPath; + return generateContextPath(config, request.getRequestContext().getStage()); } @@ -279,19 +253,7 @@ public String getRequestURI() { @Override public StringBuffer getRequestURL() { - String url = ""; - url += getServerName(); - url += cleanUri(getContextPath()); - url += cleanUri(request.getPath()); - - return new StringBuffer(getScheme() + "://" + url); - } - - - @Override - public String getServletPath() { - // we always work on the root path - return ""; + return generateRequestURL(request.getPath()); } @@ -333,7 +295,7 @@ public Part getPart(String s) @Override public T upgrade(Class aClass) throws IOException, ServletException { - return null; + throw new UnsupportedOperationException(); } //------------------------------------------------------------- @@ -343,28 +305,7 @@ public T upgrade(Class aClass) @Override public String getCharacterEncoding() { - // we only look at content-type because content-encoding should only be used for - // "binary" requests such as gzip/deflate. - String contentTypeHeader = request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE); - if (contentTypeHeader == null) { - return null; - } - - String[] contentTypeValues = contentTypeHeader.split(HEADER_VALUE_SEPARATOR); - if (contentTypeValues.length <= 1) { - return null; - } - - for (String contentTypeValue : contentTypeValues) { - if (contentTypeValue.trim().startsWith(ENCODING_VALUE_KEY)) { - String[] encodingValues = contentTypeValue.split(HEADER_KEY_VALUE_SEPARATOR); - if (encodingValues.length <= 1) { - return null; - } - return encodingValues[1]; - } - } - return null; + return parseCharacterEncoding(request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); } @@ -380,25 +321,7 @@ public void setCharacterEncoding(String s) return; } - if (currentContentType.contains(HEADER_VALUE_SEPARATOR)) { - String[] contentTypeValues = currentContentType.split(HEADER_VALUE_SEPARATOR); - StringBuilder contentType = new StringBuilder(contentTypeValues[0]); - - for (int i = 1; i < contentTypeValues.length; i++) { - String contentTypeValue = contentTypeValues[i]; - String contentTypeString = HEADER_VALUE_SEPARATOR + " " + contentTypeValue; - if (contentTypeValue.trim().startsWith(ENCODING_VALUE_KEY)) { - contentTypeString = HEADER_VALUE_SEPARATOR + " " + ENCODING_VALUE_KEY + HEADER_KEY_VALUE_SEPARATOR + s; - } - contentType.append(contentTypeString); - } - - request.getMultiValueHeaders().putSingle(HttpHeaders.CONTENT_TYPE, contentType.toString()); - } else { - request.getMultiValueHeaders().putSingle( - HttpHeaders.CONTENT_TYPE, - currentContentType + HEADER_VALUE_SEPARATOR + " " + ENCODING_VALUE_KEY + HEADER_KEY_VALUE_SEPARATOR + s); - } + request.getMultiValueHeaders().putSingle(HttpHeaders.CONTENT_TYPE, appendCharacterEncoding(currentContentType, s)); } @@ -436,33 +359,13 @@ public String getContentType() { @Override public ServletInputStream getInputStream() throws IOException { - if (request.getBody() == null) { - return new AwsServletInputStream(new NullInputStream(0, false, false)); - } - byte[] bodyBytes; - if (request.isBase64Encoded()) { - bodyBytes = Base64.getMimeDecoder().decode(request.getBody()); - } else { - String encoding = getCharacterEncoding(); - if (encoding == null) { - encoding = StandardCharsets.ISO_8859_1.name(); - } - try { - bodyBytes = request.getBody().getBytes(encoding); - } catch (Exception e) { - log.error("Could not read request with character encoding: " + SecurityUtils.crlf(encoding), e); - bodyBytes = request.getBody().getBytes(StandardCharsets.ISO_8859_1.name()); - } - - } - ByteArrayInputStream requestBodyStream = new ByteArrayInputStream(bodyBytes); - return new AwsServletInputStream(requestBodyStream); + return bodyStringToInputStream(request.getBody(), request.isBase64Encoded()); } @Override public String getParameter(String s) { - String queryStringParameter = getFirstQueryParamValue(s, config.isQueryStringCaseSensitive()); + String queryStringParameter = getFirstQueryParamValue(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive()); if (queryStringParameter != null) { return queryStringParameter; } @@ -489,7 +392,7 @@ 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<>(Arrays.asList(getQueryParamValues(s, config.isQueryStringCaseSensitive()))); + List values = new ArrayList<>(Arrays.asList(getQueryParamValues(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive()))); values.addAll(Arrays.asList(getFormBodyParameterCaseInsensitive(s))); @@ -503,26 +406,7 @@ public String[] getParameterValues(String s) { @Override public Map getParameterMap() { - Map output = new HashMap<>(); - - Map> params = getFormUrlEncodedParametersMap(); - params.entrySet().stream().parallel().forEach(e -> { - output.put(e.getKey(), e.getValue().toArray(new String[0])); - }); - - if (request.getMultiValueQueryStringParameters() != null) { - request.getMultiValueQueryStringParameters().keySet().stream().parallel().forEach(e -> { - List newValues = new ArrayList<>(); - if (output.containsKey(e)) { - String[] values = output.get(e); - newValues.addAll(Arrays.asList(values)); - } - newValues.addAll(Arrays.asList(getQueryParamValues(e, config.isQueryStringCaseSensitive()))); - output.put(e, newValues.toArray(new String[0])); - }); - } - - return output; + return generateParameterMap(request.getMultiValueQueryStringParameters(), config); } @@ -534,20 +418,7 @@ public String getProtocol() { @Override public String getScheme() { - // if we don't have any headers to deduce the value we assume HTTPS - API Gateway's default - if (request.getMultiValueHeaders() == null) { - return "https"; - } - String cfScheme = request.getMultiValueHeaders().getFirst(CF_PROTOCOL_HEADER_NAME); - if (cfScheme != null && SecurityUtils.isValidScheme(cfScheme)) { - return cfScheme; - } - String gwScheme = request.getMultiValueHeaders().getFirst(PROTOCOL_HEADER_NAME); - if (gwScheme != null && SecurityUtils.isValidScheme(gwScheme)) { - return gwScheme; - } - // https is our default scheme - return "https"; + return getSchemeFromHeader(request.getMultiValueHeaders()); } @Override @@ -705,7 +576,7 @@ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse se 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."); + + " is not in asynchronous mode. Call startAsync before attempting to get the async context."); } return asyncContext; } @@ -714,128 +585,6 @@ public AsyncContext getAsyncContext() { // Methods - Private //------------------------------------------------------------- - private String[] getFormBodyParameterCaseInsensitive(String key) { - List values = getFormUrlEncodedParametersMap().get(key); - if (values != null) { - String[] valuesArray = new String[values.size()]; - valuesArray = values.toArray(valuesArray); - return valuesArray; - } else { - return new String[0]; - } - } - - - @SuppressFBWarnings({"FILE_UPLOAD_FILENAME", "WEAK_FILENAMEUTILS"}) - private Map getMultipartFormParametersMap() { - if (multipartFormParameters != null) { - return multipartFormParameters; - } - if (!ServletFileUpload.isMultipartContent(this)) { // isMultipartContent also checks the content type - multipartFormParameters = new HashMap<>(); - return multipartFormParameters; - } - Timer.start("SERVLET_REQUEST_GET_MULTIPART_PARAMS"); - multipartFormParameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - - ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); - - try { - List items = upload.parseRequest(this); - for (FileItem item : items) { - String fileName = FilenameUtils.getName(item.getName()); - AwsProxyRequestPart newPart = new AwsProxyRequestPart(item.get()); - newPart.setName(item.getFieldName()); - newPart.setSubmittedFileName(fileName); - newPart.setContentType(item.getContentType()); - newPart.setSize(item.getSize()); - item.getHeaders().getHeaderNames().forEachRemaining(h -> { - newPart.addHeader(h, item.getHeaders().getHeader(h)); - }); - - multipartFormParameters.put(item.getFieldName(), newPart); - } - } catch (FileUploadException e) { - Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS"); - log.error("Could not read multipart upload file", e); - } - Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS"); - return multipartFormParameters; - } - - - static String cleanUri(String uri) { - String finalUri = (uri == null ? "/" : uri); - if (finalUri.equals("/")) { - return finalUri; - } - - if (!finalUri.startsWith("/")) { - finalUri = "/" + finalUri; - } - - if (finalUri.endsWith("/")) { - finalUri = finalUri.substring(0, finalUri.length() - 1); - } - - finalUri = finalUri.replaceAll("/+", "/"); - - return finalUri; - } - - - private Map> getFormUrlEncodedParametersMap() { - if (urlEncodedFormParameters != null) { - return urlEncodedFormParameters; - } - String contentType = getContentType(); - if (contentType == null) { - urlEncodedFormParameters = new HashMap<>(); - return urlEncodedFormParameters; - } - if (!contentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED) || !getMethod().toLowerCase(Locale.ENGLISH).equals("post")) { - urlEncodedFormParameters = new HashMap<>(); - return urlEncodedFormParameters; - } - Timer.start("SERVLET_REQUEST_GET_FORM_PARAMS"); - String rawBodyContent = null; - try { - rawBodyContent = IOUtils.toString(getInputStream()); - } catch (IOException e) { - throw new RuntimeException(e); - } - - urlEncodedFormParameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - for (String parameter : rawBodyContent.split(FORM_DATA_SEPARATOR)) { - String[] parameterKeyValue = parameter.split(HEADER_KEY_VALUE_SEPARATOR); - if (parameterKeyValue.length < 2) { - continue; - } - List values = new ArrayList<>(); - if (urlEncodedFormParameters.containsKey(parameterKeyValue[0])) { - values = urlEncodedFormParameters.get(parameterKeyValue[0]); - } - values.add(decodeValueIfEncoded(parameterKeyValue[1])); - urlEncodedFormParameters.put(decodeValueIfEncoded(parameterKeyValue[0]), values); - } - Timer.stop("SERVLET_REQUEST_GET_FORM_PARAMS"); - return urlEncodedFormParameters; - } - - - public static String decodeValueIfEncoded(String value) { - if (value == null) { - return null; - } - - try { - return URLDecoder.decode(value, LambdaContainerHandler.getContainerConfig().getUriEncoding()); - } catch (UnsupportedEncodingException e) { - log.warn("Could not decode body content - proceeding as if it was already decoded", e); - return value; - } - } - private List getHeaderValues(String key) { // special cases for referer and user agent headers List values = new ArrayList<>(); @@ -859,39 +608,6 @@ private List getHeaderValues(String key) { } - private String getFirstQueryParamValue(String key, boolean isCaseSensitive) { - if (request.getMultiValueQueryStringParameters() != null) { - if (isCaseSensitive) { - return request.getMultiValueQueryStringParameters().getFirst(key); - } - - for (String k : request.getMultiValueQueryStringParameters().keySet()) { - if (k.toLowerCase(Locale.getDefault()).equals(key.toLowerCase(Locale.getDefault()))) { - return request.getMultiValueQueryStringParameters().getFirst(k); - } - } - } - - return null; - } - - public String[] getQueryParamValues(String key, boolean isCaseSensitive) { - if (request.getMultiValueQueryStringParameters() != null) { - if (isCaseSensitive) { - return request.getMultiValueQueryStringParameters().get(key).toArray(new String[0]); - } - - for (String k : request.getMultiValueQueryStringParameters().keySet()) { - if (k.toLowerCase(Locale.getDefault()).equals(key.toLowerCase(Locale.getDefault()))) { - return request.getMultiValueQueryStringParameters().get(k).toArray(new String[0]); - } - } - } - - return new String[0]; - } - - public static class AwsServletInputStream extends ServletInputStream { private InputStream bodyStream; 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 405ae9739..4f6210093 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 @@ -28,6 +28,7 @@ */ 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 diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java new file mode 100644 index 000000000..066fcd4a4 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java @@ -0,0 +1,72 @@ +package com.amazonaws.serverless.proxy.model; + +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; +import java.util.HashMap; + +@JsonSerialize(using = HttpApiV2AuthorizerMap.HttpApiV2AuthorizerSerializer.class) +@JsonDeserialize(using = HttpApiV2AuthorizerMap.HttpApiV2AuthorizerDeserializer.class) +public class HttpApiV2AuthorizerMap extends HashMap { + private static final String JWT_KEY = "jwt"; + private static final long serialVersionUID = 42L; + + public HttpApiV2JwtAuthorizer getJwtAuthorizer() { + return (HttpApiV2JwtAuthorizer)get(JWT_KEY); + } + + public boolean isJwt() { + return containsKey(JWT_KEY); + } + + public void putJwtAuthorizer(HttpApiV2JwtAuthorizer jwt) { + put(JWT_KEY, jwt); + } + + public static class HttpApiV2AuthorizerDeserializer extends StdDeserializer { + private static final long serialVersionUID = 42L; + + public HttpApiV2AuthorizerDeserializer() { + super(HttpApiV2AuthorizerMap.class); + } + + @Override + public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + HttpApiV2AuthorizerMap map = new HttpApiV2AuthorizerMap(); + JsonNode node = jsonParser.getCodec().readTree(jsonParser); + if (node.get(JWT_KEY) != null) { + HttpApiV2JwtAuthorizer authorizer = LambdaContainerHandler.getObjectMapper().treeToValue(node.get(JWT_KEY), HttpApiV2JwtAuthorizer.class); + map.putJwtAuthorizer(authorizer); + } + // we ignore other, unknown values + return map; + } + } + + public static class HttpApiV2AuthorizerSerializer extends StdSerializer { + private static final long serialVersionUID = 42L; + + public HttpApiV2AuthorizerSerializer() { + super(HttpApiV2AuthorizerMap.class); + } + + @Override + public void serialize(HttpApiV2AuthorizerMap httpApiV2AuthorizerMap, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeStartObject(); + if (httpApiV2AuthorizerMap.isJwt()) { + jsonGenerator.writeObjectField(JWT_KEY, httpApiV2AuthorizerMap.getJwtAuthorizer()); + } + jsonGenerator.writeEndObject(); + } + } +} \ No newline at end of file diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java index dd0c79fe3..23d291645 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java @@ -1,23 +1,6 @@ package com.amazonaws.serverless.proxy.model; -import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; - -import java.io.IOException; -import java.util.HashMap; - public class HttpApiV2ProxyRequestContext { - private static final String JWT_KEY = "jwt"; - private String accountId; private String apiId; private String domainName; @@ -119,54 +102,4 @@ public void setAuthorizer(HttpApiV2AuthorizerMap authorizer) { this.authorizer = authorizer; } - @JsonSerialize(using = HttpApiV2AuthorizerSerializer.class) - @JsonDeserialize(using = HttpApiV2AuthorizerDeserializer.class) - public static class HttpApiV2AuthorizerMap extends HashMap { - public HttpApiV2JwtAuthorizer getJwtAuthorizer() { - return (HttpApiV2JwtAuthorizer)get(JWT_KEY); - } - - public boolean isJwt() { - return containsKey(JWT_KEY); - } - - public void putJwtAuthorizer(HttpApiV2JwtAuthorizer jwt) { - put(JWT_KEY, jwt); - } - } - - public static class HttpApiV2AuthorizerDeserializer extends StdDeserializer { - - public HttpApiV2AuthorizerDeserializer() { - super(HttpApiV2AuthorizerMap.class); - } - - @Override - public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { - HttpApiV2AuthorizerMap map = new HttpApiV2AuthorizerMap(); - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - if (node.get(JWT_KEY) != null) { - HttpApiV2JwtAuthorizer authorizer = LambdaContainerHandler.getObjectMapper().treeToValue(node.get(JWT_KEY), HttpApiV2JwtAuthorizer.class); - map.putJwtAuthorizer(authorizer); - } - // we ignore other, unknown values - return map; - } - } - - public static class HttpApiV2AuthorizerSerializer extends StdSerializer { - - public HttpApiV2AuthorizerSerializer() { - super(HttpApiV2AuthorizerMap.class); - } - - @Override - public void serialize(HttpApiV2AuthorizerMap httpApiV2AuthorizerMap, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - jsonGenerator.writeStartObject(); - if (httpApiV2AuthorizerMap.isJwt()) { - jsonGenerator.writeObjectField(JWT_KEY, httpApiV2AuthorizerMap.getJwtAuthorizer()); - } - jsonGenerator.writeEndObject(); - } - } } From 5e60887a0e0071f57ab6cf9fba72a78781d2cc59 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 31 Mar 2020 21:40:32 -0700 Subject: [PATCH 21/41] test: Basic unit tests for the new HTTP API support in core library (#329) --- .../testutils/AwsProxyRequestBuilder.java | 121 +++++++++++++-- .../jaxrs/HttpApiV2SecurityContextTest.java | 49 ++++++ ...HttpApiV2HttpServletRequestReaderTest.java | 44 ++++++ .../AwsProxyHttpServletRequestTest.java | 143 ++++++++++-------- .../model/HttpApiV2ProxyRequestTest.java | 2 +- 5 files changed, 281 insertions(+), 78 deletions(-) create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/HttpApiV2SecurityContextTest.java create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReaderTest.java 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 3260d433c..9e27d841c 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 @@ -13,15 +13,7 @@ package com.amazonaws.serverless.proxy.internal.testutils; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; -import com.amazonaws.serverless.proxy.model.AlbContext; -import com.amazonaws.serverless.proxy.model.ApiGatewayAuthorizerContext; -import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; -import com.amazonaws.serverless.proxy.model.ApiGatewayRequestIdentity; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.CognitoAuthorizerClaims; -import com.amazonaws.serverless.proxy.model.ContainerConfig; -import com.amazonaws.serverless.proxy.model.Headers; -import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; +import com.amazonaws.serverless.proxy.model.*; import com.fasterxml.jackson.core.JsonProcessingException; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -42,8 +34,13 @@ import java.net.URLEncoder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.UUID; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Collectors; /** @@ -71,6 +68,10 @@ public AwsProxyRequestBuilder(String path) { this(path, null); } + public AwsProxyRequestBuilder(AwsProxyRequest req) { + request = req; + } + public AwsProxyRequestBuilder(String path, String httpMethod) { this.request = new AwsProxyRequest(); @@ -188,6 +189,11 @@ public AwsProxyRequestBuilder header(String key, String value) { return this; } + public AwsProxyRequestBuilder multiValueHeaders(Headers h) { + this.request.setMultiValueHeaders(h); + return this; + } + public AwsProxyRequestBuilder queryString(String key, String value) { if (this.request.getMultiValueQueryStringParameters() == null) { @@ -239,6 +245,14 @@ public AwsProxyRequestBuilder body(Object body) { } } + public AwsProxyRequestBuilder apiId(String id) { + if (request.getRequestContext() == null) { + request.setRequestContext(new AwsProxyRequestContext()); + } + request.getRequestContext().setApiId(id); + return this; + } + public AwsProxyRequestBuilder binaryBody(InputStream is) throws IOException { this.request.setIsBase64Encoded(true); @@ -400,4 +414,89 @@ public InputStream buildStream() { return null; } } + + public HttpApiV2ProxyRequest toHttpApiV2Request() { + HttpApiV2ProxyRequest req = new HttpApiV2ProxyRequest(); + req.setRawPath(request.getPath()); + req.setBase64Encoded(request.isBase64Encoded()); + req.setBody(request.getBody()); + if (request.getMultiValueHeaders() != null && request.getMultiValueHeaders().containsKey(HttpHeaders.COOKIE)) { + req.setCookies(Arrays.asList(request.getMultiValueHeaders().getFirst(HttpHeaders.COOKIE).split(";"))); + } + req.setHeaders(new HashMap<>()); + if (request.getMultiValueHeaders() != null) { + request.getMultiValueHeaders().forEach((key, value) -> req.getHeaders().put(key, value.get(0))); + } + if (request.getRequestContext() != null && request.getRequestContext().getIdentity() != null) { + if (request.getRequestContext().getIdentity().getCaller() != null) { + req.getHeaders().put("Referer", request.getRequestContext().getIdentity().getCaller()); + } + if (request.getRequestContext().getIdentity().getUserAgent() != null) { + req.getHeaders().put(HttpHeaders.USER_AGENT, request.getRequestContext().getIdentity().getUserAgent()); + } + + } + if (request.getMultiValueQueryStringParameters() != null) { + StringBuilder rawQueryString = new StringBuilder(); + request.getMultiValueQueryStringParameters().forEach((k, v) -> { + for (String s : v) { + rawQueryString.append("&"); + rawQueryString.append(k); + rawQueryString.append("="); + try { + rawQueryString.append(URLEncoder.encode(s, "UTF-8")); + } catch (UnsupportedEncodingException e) { + System.out.println("Ex!"); + throw new RuntimeException(e); + } + } + }); + String qs = rawQueryString.toString(); + if (qs.length() > 1) { + req.setRawQueryString(qs.substring(1)); + } + } + req.setRouteKey("$default"); + req.setVersion("2.0"); + req.setStageVariables(request.getStageVariables()); + + HttpApiV2ProxyRequestContext ctx = new HttpApiV2ProxyRequestContext(); + HttpApiV2HttpContext httpCtx = new HttpApiV2HttpContext(); + httpCtx.setMethod(request.getHttpMethod()); + httpCtx.setPath(request.getPath()); + httpCtx.setProtocol("HTTP/1.1"); + if (request.getRequestContext() != null && request.getRequestContext().getIdentity() != null && request.getRequestContext().getIdentity().getSourceIp() != null) { + httpCtx.setSourceIp(request.getRequestContext().getIdentity().getSourceIp()); + } else { + httpCtx.setSourceIp("127.0.0.1"); + } + if (request.getRequestContext() != null && request.getRequestContext().getIdentity() != null && request.getRequestContext().getIdentity().getUserAgent() != null) { + httpCtx.setUserAgent(request.getRequestContext().getIdentity().getUserAgent()); + } + ctx.setHttp(httpCtx); + if (request.getRequestContext() != null) { + ctx.setAccountId(request.getRequestContext().getAccountId()); + ctx.setApiId(request.getRequestContext().getApiId()); + ctx.setDomainName(request.getRequestContext().getApiId() + ".execute-api.us-east-1.apigateway.com"); + ctx.setDomainPrefix(request.getRequestContext().getApiId()); + ctx.setRequestId(request.getRequestContext().getRequestId()); + ctx.setRouteKey("$default"); + ctx.setStage(request.getRequestContext().getStage()); + ctx.setTimeEpoch(request.getRequestContext().getRequestTimeEpoch()); + ctx.setTime(request.getRequestContext().getRequestTime()); + + if (request.getRequestContext().getAuthorizer() != null) { + HttpApiV2AuthorizerMap auth = new HttpApiV2AuthorizerMap(); + HttpApiV2JwtAuthorizer jwt = new HttpApiV2JwtAuthorizer(); + // TODO: Anything we should map here? + jwt.setClaims(new HashMap<>()); + jwt.setScopes(new ArrayList<>()); + auth.putJwtAuthorizer(jwt); + ctx.setAuthorizer(auth); + } + } + req.setRequestContext(ctx); + + return req; + } } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/HttpApiV2SecurityContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/HttpApiV2SecurityContextTest.java new file mode 100644 index 000000000..ae2dcdb53 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/HttpApiV2SecurityContextTest.java @@ -0,0 +1,49 @@ +package com.amazonaws.serverless.proxy.internal.jaxrs; + +import com.amazonaws.serverless.proxy.AwsHttpApiV2SecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; +import org.junit.Test; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.SecurityContext; + +import static org.junit.Assert.*; + +public class HttpApiV2SecurityContextTest { + private static final String JWT_SUB_VALUE = "1234567890"; + + HttpApiV2ProxyRequest EMPTY_AUTH = new AwsProxyRequestBuilder("/", "GET").toHttpApiV2Request(); + HttpApiV2ProxyRequest BASIC_AUTH = new AwsProxyRequestBuilder("/", "GET") + .authorizerPrincipal("test").toHttpApiV2Request(); + HttpApiV2ProxyRequest JWT_AUTH = new AwsProxyRequestBuilder("/", "GET") + .authorizerPrincipal("test") + .header(HttpHeaders.AUTHORIZATION, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c") + .toHttpApiV2Request(); + + AwsHttpApiV2SecurityContextWriter contextWriter = new AwsHttpApiV2SecurityContextWriter(); + + @Test + public void getAuthenticationScheme_nullAuth_nullSchema() { + SecurityContext ctx = contextWriter.writeSecurityContext(EMPTY_AUTH, null); + assertNull(ctx.getAuthenticationScheme()); + assertNull(ctx.getUserPrincipal()); + assertFalse(ctx.isSecure()); + } + + @Test + public void getAuthenticationScheme_jwtAuth_correctSchema() { + SecurityContext ctx = contextWriter.writeSecurityContext(BASIC_AUTH, null); + assertEquals(AwsHttpApiV2SecurityContext.AUTH_SCHEME_JWT, ctx.getAuthenticationScheme()); + assertTrue(ctx.isSecure()); + assertNull(ctx.getUserPrincipal()); + } + + @Test + public void getPrincipal_parseJwt_returnsSub() { + SecurityContext ctx = contextWriter.writeSecurityContext(JWT_AUTH, null); + assertEquals(AwsHttpApiV2SecurityContext.AUTH_SCHEME_JWT, ctx.getAuthenticationScheme()); + assertTrue(ctx.isSecure()); + assertEquals(JWT_SUB_VALUE, ctx.getUserPrincipal().getName()); + } +} diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReaderTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReaderTest.java new file mode 100644 index 000000000..004d4d489 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReaderTest.java @@ -0,0 +1,44 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import com.amazonaws.serverless.exceptions.InvalidRequestEventException; +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequestContext; +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; + +import static org.junit.Assert.*; + +public class AwsHttpApiV2HttpServletRequestReaderTest { + private AwsHttpApiV2HttpServletRequestReader reader = new AwsHttpApiV2HttpServletRequestReader(); + + @Test + public void reflection_getRequestClass_returnsCorrectType() { + assertSame(HttpApiV2ProxyRequest.class, reader.getRequestClass()); + } + + @Test + public void baseRequest_read_populatesSuccessfully() { + HttpApiV2ProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET") + .referer("localhost") + .queryString("param1", "value1") + .header("custom", "value") + .apiId("test").toHttpApiV2Request(); + AwsHttpApiV2HttpServletRequestReader reader = new AwsHttpApiV2HttpServletRequestReader(); + try { + HttpServletRequest servletRequest = reader.readRequest(req, null, null, LambdaContainerHandler.getContainerConfig()); + assertEquals("/hello", servletRequest.getPathInfo()); + assertEquals("value1", servletRequest.getParameter("param1")); + assertEquals("value", servletRequest.getHeader("CUSTOM")); + + assertNotNull(servletRequest.getAttribute(AwsHttpApiV2HttpServletRequestReader.HTTP_API_CONTEXT_PROPERTY)); + assertEquals("test", + ((HttpApiV2ProxyRequestContext)servletRequest.getAttribute(AwsHttpApiV2HttpServletRequestReader.HTTP_API_CONTEXT_PROPERTY)).getApiId()); + } catch (InvalidRequestEventException e) { + e.printStackTrace(); + fail("Could not read request"); + } + } +} 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 1e1088226..47440eb9d 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 @@ -23,10 +23,11 @@ import java.util.*; import static org.junit.Assert.*; +import static org.junit.Assume.assumeFalse; @RunWith(Parameterized.class) public class AwsProxyHttpServletRequestTest { - private boolean isWrapped; + private String requestType; private static final String CUSTOM_HEADER_KEY = "X-Custom-Header"; private static final String CUSTOM_HEADER_VALUE = "Custom-Header-Value"; @@ -39,58 +40,65 @@ public class AwsProxyHttpServletRequestTest { private static final String REFERER = "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox"; private static ZonedDateTime REQUEST_DATE = ZonedDateTime.now(); - private static final AwsProxyRequest REQUEST_FORM_URLENCODED = new AwsProxyRequestBuilder("/hello", "POST") - .form(FORM_PARAM_NAME, FORM_PARAM_NAME_VALUE).build(); - private static final AwsProxyRequest REQUEST_INVALID_FORM_URLENCODED = new AwsProxyRequestBuilder("/hello", "GET") - .form(FORM_PARAM_NAME, FORM_PARAM_NAME_VALUE).build(); - private static final AwsProxyRequest REQUEST_FORM_URLENCODED_AND_QUERY = new AwsProxyRequestBuilder("/hello", "POST") + private static final AwsProxyRequestBuilder REQUEST_FORM_URLENCODED = new AwsProxyRequestBuilder("/hello", "POST") + .form(FORM_PARAM_NAME, FORM_PARAM_NAME_VALUE); + private static final AwsProxyRequestBuilder REQUEST_INVALID_FORM_URLENCODED = new AwsProxyRequestBuilder("/hello", "GET") + .form(FORM_PARAM_NAME, FORM_PARAM_NAME_VALUE); + private static final AwsProxyRequestBuilder REQUEST_FORM_URLENCODED_AND_QUERY = new AwsProxyRequestBuilder("/hello", "POST") .form(FORM_PARAM_NAME, FORM_PARAM_NAME_VALUE) - .queryString(FORM_PARAM_NAME, QUERY_STRING_NAME_VALUE).build(); - private static final AwsProxyRequest REQUEST_SINGLE_COOKIE = new AwsProxyRequestBuilder("/hello", "GET") - .cookie(FORM_PARAM_NAME, FORM_PARAM_NAME_VALUE).build(); - private static final AwsProxyRequest REQUEST_MULTIPLE_COOKIES = new AwsProxyRequestBuilder("/hello", "GET") + .queryString(FORM_PARAM_NAME, QUERY_STRING_NAME_VALUE); + private static final AwsProxyRequestBuilder REQUEST_SINGLE_COOKIE = new AwsProxyRequestBuilder("/hello", "GET") + .cookie(FORM_PARAM_NAME, FORM_PARAM_NAME_VALUE); + private static final AwsProxyRequestBuilder REQUEST_MULTIPLE_COOKIES = new AwsProxyRequestBuilder("/hello", "GET") .cookie(FORM_PARAM_NAME, FORM_PARAM_NAME_VALUE) - .cookie(FORM_PARAM_TEST, FORM_PARAM_NAME_VALUE).build(); - private static final AwsProxyRequest REQUEST_MALFORMED_COOKIE = new AwsProxyRequestBuilder("/hello", "GET") - .header(HttpHeaders.COOKIE, QUERY_STRING_NAME_VALUE).build(); - private static final AwsProxyRequest REQUEST_MULTIPLE_FORM_AND_QUERY = new AwsProxyRequestBuilder("/hello", "POST") + .cookie(FORM_PARAM_TEST, FORM_PARAM_NAME_VALUE); + private static final AwsProxyRequestBuilder REQUEST_MALFORMED_COOKIE = new AwsProxyRequestBuilder("/hello", "GET") + .header(HttpHeaders.COOKIE, QUERY_STRING_NAME_VALUE); + private static final AwsProxyRequestBuilder REQUEST_MULTIPLE_FORM_AND_QUERY = new AwsProxyRequestBuilder("/hello", "POST") .form(FORM_PARAM_NAME, FORM_PARAM_NAME_VALUE) - .queryString(FORM_PARAM_TEST, QUERY_STRING_NAME_VALUE).build(); - private static final AwsProxyRequest REQUEST_USER_AGENT_REFERER = new AwsProxyRequestBuilder("/hello", "POST") + .queryString(FORM_PARAM_TEST, QUERY_STRING_NAME_VALUE); + private static final AwsProxyRequestBuilder REQUEST_USER_AGENT_REFERER = new AwsProxyRequestBuilder("/hello", "POST") .userAgent(USER_AGENT) - .referer(REFERER).build(); - private static final AwsProxyRequest REQUEST_WITH_DATE = new AwsProxyRequestBuilder("/hello", "GET") - .header(HttpHeaders.DATE, AwsHttpServletRequest.dateFormatter.format(REQUEST_DATE)) - .build(); - private static final AwsProxyRequest REQUEST_WITH_LOWERCASE_HEADER = new AwsProxyRequestBuilder("/hello", "POST") - .header(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault()), MediaType.APPLICATION_JSON).build(); - - private static final AwsProxyRequest REQUEST_NULL_QUERY_STRING; + .referer(REFERER); + private static final AwsProxyRequestBuilder REQUEST_WITH_DATE = new AwsProxyRequestBuilder("/hello", "GET") + .header(HttpHeaders.DATE, AwsHttpServletRequest.dateFormatter.format(REQUEST_DATE)); + private static final AwsProxyRequestBuilder REQUEST_WITH_LOWERCASE_HEADER = new AwsProxyRequestBuilder("/hello", "POST") + .header(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault()), MediaType.APPLICATION_JSON); + + private static final AwsProxyRequestBuilder REQUEST_NULL_QUERY_STRING; static { AwsProxyRequest awsProxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); awsProxyRequest.setMultiValueQueryStringParameters(null); - REQUEST_NULL_QUERY_STRING = awsProxyRequest; + REQUEST_NULL_QUERY_STRING = new AwsProxyRequestBuilder(awsProxyRequest); } - private static final AwsProxyRequest REQUEST_QUERY = new AwsProxyRequestBuilder("/hello", "POST") - .queryString(FORM_PARAM_NAME, QUERY_STRING_NAME_VALUE).build(); + private static final AwsProxyRequestBuilder REQUEST_QUERY = new AwsProxyRequestBuilder("/hello", "POST") + .queryString(FORM_PARAM_NAME, QUERY_STRING_NAME_VALUE); - public AwsProxyHttpServletRequestTest(boolean wrap) { - isWrapped = wrap; + public AwsProxyHttpServletRequestTest(String type) { + requestType = type; } @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 Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API", "WRAP" }); + } + + private HttpServletRequest getRequest(AwsProxyRequestBuilder req, Context lambdaCtx, SecurityContext securityCtx) { + switch (requestType) { + case "API_GW": + return new AwsProxyHttpServletRequest(req.build(), lambdaCtx, securityCtx); + case "ALB": + return new AwsProxyHttpServletRequest(req.alb().build(), lambdaCtx, securityCtx); + case "HTTP_API": + return new AwsHttpApiV2ProxyHttpServletRequest(req.toHttpApiV2Request(), lambdaCtx, securityCtx, LambdaContainerHandler.getContainerConfig()); + case "WRAP": + HttpServletRequest servletRequest = new AwsProxyHttpServletRequest(req.build(), lambdaCtx, securityCtx); + return new AwsHttpServletRequestWrapper(servletRequest, req.build().getPath()); + default: + throw new RuntimeException("Unknown test variant: " + requestType); } - return servletRequest; } @@ -104,6 +112,7 @@ public void headers_getHeader_validRequest() { @Test public void headers_getRefererAndUserAgent_returnsContextValues() { + assumeFalse("ALB".equals(requestType)); HttpServletRequest request = getRequest(REQUEST_USER_AGENT_REFERER, null, null); assertNotNull(request.getHeader("Referer")); assertEquals(REFERER, request.getHeader("Referer")); @@ -344,7 +353,7 @@ public void contentType_lowerCaseHeaderKey_expectUpdatedMediaType() { @Test public void contentType_duplicateCase_expectSingleContentTypeHeader() { - AwsProxyRequest proxyRequest = getRequestWithHeaders(); + AwsProxyRequestBuilder proxyRequest = getRequestWithHeaders(); HttpServletRequest request = getRequest(proxyRequest, null, null); try { @@ -359,8 +368,9 @@ public void contentType_duplicateCase_expectSingleContentTypeHeader() { @Test public void requestURL_getUrl_expectHttpSchemaAndLocalhostForLocalTesting() { - AwsProxyRequest req = getRequestWithHeaders(); - req.getRequestContext().setApiId("test-id"); + assumeFalse("ALB".equals(requestType)); + AwsProxyRequestBuilder req = getRequestWithHeaders(); + req.apiId("test-id"); LambdaContainerHandler.getContainerConfig().enableLocalhost(); HttpServletRequest servletRequest = getRequest(req, null, null); String requestUrl = servletRequest.getRequestURL().toString(); @@ -369,7 +379,7 @@ public void requestURL_getUrl_expectHttpSchemaAndLocalhostForLocalTesting() { assertTrue(requestUrl.endsWith(".com/hello")); // set localhost - req.getMultiValueHeaders().putSingle("Host", "localhost"); + req.header("Host", "localhost"); servletRequest = getRequest(req, null, null); requestUrl = servletRequest.getRequestURL().toString(); assertTrue(requestUrl.contains("http://localhost")); @@ -379,7 +389,7 @@ public void requestURL_getUrl_expectHttpSchemaAndLocalhostForLocalTesting() { @Test public void requestURL_getUrlWithCustomBasePath_expectCustomBasePath() { - AwsProxyRequest req = getRequestWithHeaders(); + AwsProxyRequestBuilder req = getRequestWithHeaders(); LambdaContainerHandler.getContainerConfig().setServiceBasePath("test"); HttpServletRequest servletRequest = getRequest(req, null, null); String requestUrl = servletRequest.getRequestURL().toString(); @@ -389,18 +399,20 @@ public void requestURL_getUrlWithCustomBasePath_expectCustomBasePath() { @Test public void requestURL_getUrlWithContextPath_expectStageAsContextPath() { - AwsProxyRequest req = getRequestWithHeaders(); - req.getRequestContext().setStage("test-stage"); + assumeFalse("ALB".equals(requestType)); + AwsProxyRequestBuilder req = getRequestWithHeaders(); + req.stage("test-stage"); LambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(true); HttpServletRequest servletRequest = getRequest(req, null, null); String requestUrl = servletRequest.getRequestURL().toString(); + System.out.println(requestUrl); assertTrue(requestUrl.contains("/test-stage/")); LambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(false); } @Test public void getLocales_emptyAcceptHeader_expectDefaultLocale() { - AwsProxyRequest req = getRequestWithHeaders(); + AwsProxyRequestBuilder req = getRequestWithHeaders(); HttpServletRequest servletRequest = getRequest(req, null, null); Enumeration locales = servletRequest.getLocales(); int localesNo = 0; @@ -414,8 +426,8 @@ public void getLocales_emptyAcceptHeader_expectDefaultLocale() { @Test public void getLocales_validAcceptHeader_expectSingleLocale() { - AwsProxyRequest req = getRequestWithHeaders(); - req.getMultiValueHeaders().putSingle(HttpHeaders.ACCEPT_LANGUAGE, "fr-CH"); + AwsProxyRequestBuilder req = getRequestWithHeaders(); + req.header(HttpHeaders.ACCEPT_LANGUAGE, "fr-CH"); HttpServletRequest servletRequest = getRequest(req, null, null); Enumeration locales = servletRequest.getLocales(); int localesNo = 0; @@ -429,8 +441,8 @@ public void getLocales_validAcceptHeader_expectSingleLocale() { @Test 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"); + AwsProxyRequestBuilder req = getRequestWithHeaders(); + req.header(HttpHeaders.ACCEPT_LANGUAGE, "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5"); HttpServletRequest servletRequest = getRequest(req, null, null); Enumeration locales = servletRequest.getLocales(); List localesList = new ArrayList<>(); @@ -450,8 +462,8 @@ public void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleList() { @Test 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"); + AwsProxyRequestBuilder req = getRequestWithHeaders(); + req.header(HttpHeaders.ACCEPT_LANGUAGE, "fr-CH, en;q=0.8, de;q=0.7, *;q=0.5, fr;q=0.9"); HttpServletRequest servletRequest = getRequest(req, null, null); Enumeration locales = servletRequest.getLocales(); List localesList = new ArrayList<>(); @@ -468,7 +480,7 @@ public void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleListOrde @Test public void nullQueryString_expectNoExceptions() { - AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/hello", "GET"); HttpServletRequest servletReq = getRequest(req, null, null); assertNull(servletReq.getQueryString()); assertEquals(0, servletReq.getParameterMap().size()); @@ -479,8 +491,8 @@ public void nullQueryString_expectNoExceptions() { @Test public void inputStream_emptyBody_expectNullInputStream() { - AwsProxyRequest proxyReq = getRequestWithHeaders(); - assertNull(proxyReq.getBody()); + AwsProxyRequestBuilder proxyReq = getRequestWithHeaders(); + assertNull(proxyReq.build().getBody()); HttpServletRequest req = getRequest(proxyReq, null, null); try { @@ -494,7 +506,7 @@ public void inputStream_emptyBody_expectNullInputStream() { @Test public void getHeaders_emptyHeaders_expectEmptyEnumeration() { - AwsProxyRequest proxyReq = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyRequestBuilder proxyReq = new AwsProxyRequestBuilder("/hello", "GET"); HttpServletRequest req = getRequest(proxyReq, null, null); assertFalse(req.getHeaders("param").hasMoreElements()); } @@ -507,24 +519,24 @@ public void getServerPort_defaultPort_expect443() { @Test public void getServerPort_customPortFromHeader_expectCustomPort() { - AwsProxyRequest proxyReq = getRequestWithHeaders(); - proxyReq.getMultiValueHeaders().putSingle(AwsProxyHttpServletRequest.PORT_HEADER_NAME, "80"); + AwsProxyRequestBuilder proxyReq = getRequestWithHeaders(); + proxyReq.header(AwsProxyHttpServletRequest.PORT_HEADER_NAME, "80"); HttpServletRequest req = getRequest(proxyReq, null, null); assertEquals(80, req.getServerPort()); } @Test public void getServerPort_invalidCustomPortFromHeader_expectDefaultPort() { - AwsProxyRequest proxyReq = getRequestWithHeaders(); - proxyReq.getMultiValueHeaders().putSingle(AwsProxyHttpServletRequest.PORT_HEADER_NAME, "7200"); + AwsProxyRequestBuilder proxyReq = getRequestWithHeaders(); + proxyReq.header(AwsProxyHttpServletRequest.PORT_HEADER_NAME, "7200"); HttpServletRequest req = getRequest(proxyReq, null, null); assertEquals(443, req.getServerPort()); } @Test public void serverName_emptyHeaders_doesNotThrowNullPointer() { - AwsProxyRequest proxyReq = new AwsProxyRequestBuilder("/test", "GET").build(); - proxyReq.setMultiValueHeaders(null); + AwsProxyRequestBuilder proxyReq = new AwsProxyRequestBuilder("/test", "GET"); + proxyReq.multiValueHeaders(null); HttpServletRequest servletReq = getRequest(proxyReq, null, null); String serverName = servletReq.getServerName(); assertTrue(serverName.startsWith("null.execute-api")); @@ -532,19 +544,18 @@ public void serverName_emptyHeaders_doesNotThrowNullPointer() { @Test public void serverName_hostHeader_returnsHostHeaderOnly() { - AwsProxyRequest proxyReq = new AwsProxyRequestBuilder("/test", "GET") - .header(HttpHeaders.HOST, "testapi.com").build(); + AwsProxyRequestBuilder proxyReq = new AwsProxyRequestBuilder("/test", "GET") + .header(HttpHeaders.HOST, "testapi.com"); LambdaContainerHandler.getContainerConfig().addCustomDomain("testapi.com"); HttpServletRequest servletReq = getRequest(proxyReq, null, null); String serverName = servletReq.getServerName(); assertEquals("testapi.com", serverName); } - private AwsProxyRequest getRequestWithHeaders() { + private AwsProxyRequestBuilder getRequestWithHeaders() { return new AwsProxyRequestBuilder("/hello", "GET") .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .header(AwsProxyHttpServletRequest.CF_PROTOCOL_HEADER_NAME, REQUEST_SCHEME_HTTP) - .build(); + .header(AwsProxyHttpServletRequest.CF_PROTOCOL_HEADER_NAME, REQUEST_SCHEME_HTTP); } } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java index 4c7be277a..420843300 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java @@ -126,7 +126,7 @@ public void serialize_toJsonString_authorizerPopulatesCorrectly() { HttpApiV2ProxyRequest req = new HttpApiV2ProxyRequest(); req.setBase64Encoded(false); req.setRequestContext(new HttpApiV2ProxyRequestContext()); - req.getRequestContext().setAuthorizer(new HttpApiV2ProxyRequestContext.HttpApiV2AuthorizerMap()); + req.getRequestContext().setAuthorizer(new HttpApiV2AuthorizerMap()); req.getRequestContext().getAuthorizer().putJwtAuthorizer(new HttpApiV2JwtAuthorizer()); ArrayList scopes = new ArrayList<>(); scopes.add("first"); From 076f955934466f5c49c5a46414497513d05922f9 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 1 Apr 2020 09:20:33 -0700 Subject: [PATCH 22/41] feat: Updated log formatter to support both versions (1 and 2) of the proxy request model (#329) --- .../ApacheCombinedServletLogFormatter.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatter.java index 2105ffae3..66605aded 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatter.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatter.java @@ -5,6 +5,7 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequestContext; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import javax.servlet.http.HttpServletRequest; @@ -16,7 +17,7 @@ import java.time.format.DateTimeFormatterBuilder; import java.util.Locale; -import static com.amazonaws.serverless.proxy.RequestReader.API_GATEWAY_EVENT_PROPERTY; +import static com.amazonaws.serverless.proxy.RequestReader.*; import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoField.HOUR_OF_DAY; import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; @@ -68,45 +69,45 @@ public ApacheCombinedServletLogFormatter() { public String format(ContainerRequestType servletRequest, ContainerResponseType servletResponse, SecurityContext ctx) { //LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" combined StringBuilder logLineBuilder = new StringBuilder(); - AwsProxyRequest req = (AwsProxyRequest)servletRequest.getAttribute(API_GATEWAY_EVENT_PROPERTY); - AwsProxyRequestContext gatewayContext = req.getRequestContext(); + AwsProxyRequestContext gatewayContext = (AwsProxyRequestContext)servletRequest.getAttribute(API_GATEWAY_CONTEXT_PROPERTY); + HttpApiV2ProxyRequestContext httpApiContext = (HttpApiV2ProxyRequestContext)servletRequest.getAttribute(HTTP_API_CONTEXT_PROPERTY); // %h logLineBuilder.append(servletRequest.getRemoteAddr()); logLineBuilder.append(" "); // %l - if (gatewayContext != null && req.getRequestSource() == AwsProxyRequest.RequestSource.API_GATEWAY) { - if (gatewayContext.getIdentity().getUserArn() != null) { + if (servletRequest.getUserPrincipal() != null) { + logLineBuilder.append(servletRequest.getUserPrincipal().getName()); + } else { + logLineBuilder.append("-"); + } + if (gatewayContext != null && gatewayContext.getIdentity() != null && gatewayContext.getIdentity().getUserArn() != null) { logLineBuilder.append(gatewayContext.getIdentity().getUserArn()); - } else { - logLineBuilder.append("-"); - } } else { logLineBuilder.append("-"); } logLineBuilder.append(" "); // %u - if (ctx != null && ctx.getUserPrincipal().getName() != null) { - logLineBuilder.append(ctx.getUserPrincipal().getName()); - logLineBuilder.append(" "); + if (servletRequest.getUserPrincipal() != null) { + logLineBuilder.append(servletRequest.getUserPrincipal().getName()); } + logLineBuilder.append(" "); // %t + long timeEpoch = ZonedDateTime.now(clock).toEpochSecond(); if (gatewayContext != null && gatewayContext.getRequestTimeEpoch() > 0) { - logLineBuilder.append( - dateFormat.format( - ZonedDateTime.of( - LocalDateTime.ofEpochSecond(gatewayContext.getRequestTimeEpoch() / 1000, 0, ZoneOffset.UTC), - clock.getZone() - ) - ) - ); - } else { - logLineBuilder.append(dateFormat.format(ZonedDateTime.now(clock))); + timeEpoch = gatewayContext.getRequestTimeEpoch() / 1000; + } else if (httpApiContext != null && httpApiContext.getTimeEpoch() > 0) { + timeEpoch = httpApiContext.getTimeEpoch() / 1000; } + logLineBuilder.append( + dateFormat.format(ZonedDateTime.of( + LocalDateTime.ofEpochSecond(timeEpoch, 0, ZoneOffset.UTC), + clock.getZone()) + )); logLineBuilder.append(" "); // %r @@ -155,7 +156,6 @@ public String format(ContainerRequestType servletRequest, ContainerResponseType logLineBuilder.append("combined"); - return logLineBuilder.toString(); } } From 40614e34454676850bdab3d971031e9486c579db Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 1 Apr 2020 09:22:56 -0700 Subject: [PATCH 23/41] feat: Further generified request readers to read to a generic HttpServletRequest rather than specific implementations of it. This makes it easier to create container handler implementations that support HTTP API, API Gateway, and ALB (#329) --- .../AwsHttpApiV2HttpServletRequestReader.java | 5 +++-- .../AwsProxyHttpServletRequestReader.java | 5 +++-- .../AwsProxyHttpServletResponseWriter.java | 2 +- .../internal/servlet/AwsAsyncContextTest.java | 6 ++--- .../AwsProxyHttpServletRequestReaderTest.java | 6 ++--- .../AwsProxyRequestDispatcherTest.java | 22 +++++++++---------- ...vletLambdaContainerHandlerBuilderTest.java | 9 ++++---- 7 files changed, 29 insertions(+), 26 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java index bb4528023..2ed16dd9b 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java @@ -6,13 +6,14 @@ import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; import com.amazonaws.services.lambda.runtime.Context; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.SecurityContext; -public class AwsHttpApiV2HttpServletRequestReader extends RequestReader { +public class AwsHttpApiV2HttpServletRequestReader extends RequestReader { static final String INVALID_REQUEST_ERROR = "The incoming event is not a valid HTTP API v2 proxy request"; @Override - public AwsHttpApiV2ProxyHttpServletRequest readRequest(HttpApiV2ProxyRequest request, SecurityContext securityContext, Context lambdaContext, ContainerConfig config) throws InvalidRequestEventException { + public HttpServletRequest readRequest(HttpApiV2ProxyRequest request, SecurityContext securityContext, Context lambdaContext, ContainerConfig config) throws InvalidRequestEventException { if (request.getRequestContext() == null || request.getRequestContext().getHttp().getMethod() == null || request.getRequestContext().getHttp().getMethod().equals("")) { throw new InvalidRequestEventException(INVALID_REQUEST_ERROR); } 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 4f6210093..932041db9 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 @@ -19,6 +19,7 @@ import com.amazonaws.services.lambda.runtime.Context; import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.SecurityContext; @@ -26,7 +27,7 @@ * Simple implementation of the RequestReader interface that receives an AwsProxyRequest * object and uses it to initialize a AwsProxyHttpServletRequest object. */ -public class AwsProxyHttpServletRequestReader extends RequestReader { +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; @@ -39,7 +40,7 @@ public void setServletContext(ServletContext ctx) { } @Override - public AwsProxyHttpServletRequest readRequest(AwsProxyRequest request, SecurityContext securityContext, Context lambdaContext, ContainerConfig config) + public HttpServletRequest readRequest(AwsProxyRequest request, SecurityContext securityContext, Context lambdaContext, ContainerConfig config) throws InvalidRequestEventException { // Expect the HTTP method and context to be populated. If they are not, we are handling an // unsupported event type. diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java index 32a86f667..ba98eedb2 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java @@ -57,7 +57,7 @@ public AwsProxyResponse writeResponse(AwsHttpServletResponse containerResponse, awsProxyResponse.setStatusCode(containerResponse.getStatus()); - if (containerResponse.getAwsProxyRequest().getRequestSource() == AwsProxyRequest.RequestSource.ALB) { + if (containerResponse.getAwsProxyRequest() != null && containerResponse.getAwsProxyRequest().getRequestSource() == AwsProxyRequest.RequestSource.ALB) { awsProxyResponse.setStatusDescription(containerResponse.getStatus() + " " + Response.Status.fromStatusCode(containerResponse.getStatus()).getReasonPhrase()); } 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 349a95497..46a79160a 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 @@ -83,7 +83,7 @@ private AwsServletContext getCtx() { return ctx; } - public static class MockContainerHandler extends AwsLambdaServletContainerHandler { + public static class MockContainerHandler extends AwsLambdaServletContainerHandler { private int desiredStatus; private HttpServletResponse response; private Servlet selectedServlet; @@ -94,7 +94,7 @@ public MockContainerHandler() { } @Override - protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { return new AwsHttpServletResponse(request, latch); } @@ -115,7 +115,7 @@ protected void doFilter(HttpServletRequest request, HttpServletResponse response } @Override - protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + protected void handleRequest(HttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { } 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 de246bc04..6b9caf9b1 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 @@ -114,7 +114,7 @@ public void readRequest_contentCharset_appendsCharsetToComplextContentType() { public void readRequest_validEventEmptyPath_expectException() { try { AwsProxyRequest req = new AwsProxyRequestBuilder(null, "GET").build(); - AwsProxyHttpServletRequest servletReq = reader.readRequest(req, null, null, ContainerConfig.defaultConfig()); + HttpServletRequest servletReq = reader.readRequest(req, null, null, ContainerConfig.defaultConfig()); assertNotNull(servletReq); } catch (InvalidRequestEventException e) { e.printStackTrace(); @@ -150,7 +150,7 @@ public void readRequest_nullHeaders_expectSuccess() { AwsProxyRequest req = new AwsProxyRequestBuilder("/path", "GET").build(); req.setMultiValueHeaders(null); try { - AwsProxyHttpServletRequest servletReq = reader.readRequest(req, null, null, ContainerConfig.defaultConfig()); + HttpServletRequest servletReq = reader.readRequest(req, null, null, ContainerConfig.defaultConfig()); String headerValue = servletReq.getHeader(HttpHeaders.CONTENT_TYPE); assertNull(headerValue); } catch (InvalidRequestEventException e) { @@ -163,7 +163,7 @@ public void readRequest_nullHeaders_expectSuccess() { public void readRequest_emptyHeaders_expectSuccess() { AwsProxyRequest req = new AwsProxyRequestBuilder("/path", "GET").build(); try { - AwsProxyHttpServletRequest servletReq = reader.readRequest(req, null, null, ContainerConfig.defaultConfig()); + HttpServletRequest servletReq = reader.readRequest(req, null, null, ContainerConfig.defaultConfig()); String headerValue = servletReq.getHeader(HttpHeaders.CONTENT_TYPE); assertNull(headerValue); } catch (InvalidRequestEventException e) { 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 898f251bf..d061a1115 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 @@ -33,7 +33,7 @@ public class AwsProxyRequestDispatcherTest { @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()); + HttpServletRequest servletRequest = requestReader.readRequest(proxyRequest,null, new MockLambdaContext(), ContainerConfig.defaultConfig()); AwsProxyRequestDispatcher dispatcher = new AwsProxyRequestDispatcher(FORWARD_PATH, false, null); dispatcher.setRequestPath(servletRequest, FORWARD_PATH); @@ -43,7 +43,7 @@ public void setPath_forwardByPath_proxyRequestObjectInPropertyReferencesSameProx @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()); + HttpServletRequest servletRequest = requestReader.readRequest(proxyRequest,null, new MockLambdaContext(), ContainerConfig.defaultConfig()); SecurityContextHolderAwareRequestWrapper springSecurityRequest = new SecurityContextHolderAwareRequestWrapper(servletRequest, "ADMIN"); AwsProxyRequestDispatcher dispatcher = new AwsProxyRequestDispatcher(FORWARD_PATH, false, null); @@ -70,7 +70,7 @@ public void setPathForWrappedRequestWithoutGatewayEvent_forwardByPath_throwsExce @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()); + HttpServletRequest 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))); @@ -88,7 +88,7 @@ public void forwardRequest_nullHandler_throwsIllegalStateException() throws Inva @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()); + HttpServletRequest 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)); @@ -109,7 +109,7 @@ public void forwardRequest_committedResponse_throwsIllegalStateException() throw @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()); + HttpServletRequest 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)); @@ -174,8 +174,8 @@ private interface RequestHandler { } - private AwsLambdaServletContainerHandler mockLambdaHandler(RequestHandler h) { - return new AwsLambdaServletContainerHandler( + private AwsLambdaServletContainerHandler mockLambdaHandler(RequestHandler h) { + return new AwsLambdaServletContainerHandler( AwsProxyRequest.class, AwsProxyResponse.class, new AwsProxyHttpServletRequestReader(), @@ -192,18 +192,18 @@ protected void doFilter(HttpServletRequest request, HttpServletResponse response } @Override - protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { return new AwsHttpServletResponse(request, latch); } @Override - protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + protected void handleRequest(HttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { if (h != null) { setServletContext(new AwsServletContext(this)); - containerRequest.setServletContext(getServletContext()); + ((AwsHttpServletRequest)containerRequest).setServletContext(getServletContext()); - h.handleRequest(containerRequest, containerResponse); + h.handleRequest((AwsProxyHttpServletRequest)containerRequest, containerResponse); } containerResponse.flushBuffer(); } 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 index a9fef9b19..1481afc7e 100644 --- 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 @@ -8,6 +8,7 @@ import com.amazonaws.services.lambda.runtime.Context; import org.junit.Test; +import javax.servlet.http.HttpServletRequest; import java.util.concurrent.CountDownLatch; import static org.junit.Assert.*; @@ -44,18 +45,18 @@ public void defaultProxy_setsValuesCorrectly() { assertEquals("test", test.name); } - public static final class TestHandler extends AwsLambdaServletContainerHandler { + 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) { + protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { return null; } @Override - protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + protected void handleRequest(HttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { } @@ -69,7 +70,7 @@ public static final class TestBuilder extends ServletLambdaContainerHandlerBuilder< AwsProxyRequest, AwsProxyResponse, - AwsProxyHttpServletRequest, + HttpServletRequest, TestHandler, TestBuilder> { From 8837570532a0dc5ae72d154491cc36e82deb58a5 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 1 Apr 2020 09:31:07 -0700 Subject: [PATCH 24/41] test: Fixed tests for new logged and generified request readers --- .../servlet/ApacheCombinedServletLogFormatterTest.java | 9 +++++---- .../servlet/AwsProxyHttpServletRequestReaderTest.java | 7 ------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatterTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatterTest.java index 1c9527a32..0d12a5116 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatterTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatterTest.java @@ -14,6 +14,7 @@ import java.time.Instant; import java.time.ZoneId; +import static com.amazonaws.serverless.proxy.RequestReader.API_GATEWAY_CONTEXT_PROPERTY; import static com.amazonaws.serverless.proxy.RequestReader.API_GATEWAY_EVENT_PROPERTY; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertThat; @@ -35,13 +36,13 @@ public void setup() { proxyRequest = new AwsProxyRequest(); Clock fixedClock = Clock.fixed(Instant.ofEpochSecond(665888523L), ZoneId.of("UTC")); mockServletRequest = mock(HttpServletRequest.class); - when(mockServletRequest.getAttribute(eq(API_GATEWAY_EVENT_PROPERTY))) - .thenReturn(proxyRequest); + context = new AwsProxyRequestContext(); + context.setIdentity(new ApiGatewayRequestIdentity()); + when(mockServletRequest.getAttribute(eq(API_GATEWAY_CONTEXT_PROPERTY))) + .thenReturn(context); when(mockServletRequest.getMethod()) .thenReturn("GET"); mockServletResponse = mock(HttpServletResponse.class); - context = new AwsProxyRequestContext(); - context.setIdentity(new ApiGatewayRequestIdentity()); proxyRequest.setRequestContext(context); sut = new ApacheCombinedServletLogFormatter(fixedClock); 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 6b9caf9b1..166bfb472 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 @@ -25,13 +25,6 @@ public class AwsProxyHttpServletRequestReaderTest { private static final String ENCODED_REQUEST_PATH = "/foo/bar/Some%20Thing"; private static final String DECODED_REQUEST_PATH = "/foo/bar/Some Thing"; - @Test - public void readRequest_reflection_returnType() throws NoSuchMethodException { - Method readRequestMethod = AwsProxyHttpServletRequestReader.class.getMethod("readRequest", AwsProxyRequest.class, SecurityContext.class, Context.class, ContainerConfig.class); - - assertTrue(readRequestMethod.getReturnType() == AwsProxyHttpServletRequest.class); - } - @Test public void readRequest_validAwsProxy_populatedRequest() { AwsProxyRequest request = new AwsProxyRequestBuilder("/path", "GET").header(TEST_HEADER_KEY, TEST_HEADER_VALUE).build(); From 246b857dc247ff713ea97bcd83dd3a6a3a83aeaf Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 1 Apr 2020 09:35:32 -0700 Subject: [PATCH 25/41] feat: Added HTTP API support to Jersey implementation with new getHttpApiV2ProxyHandler method (#329) --- .../testutils/AwsProxyRequestBuilder.java | 15 ++ .../jersey/JerseyLambdaContainerHandler.java | 71 ++++-- .../proxy/jersey/JerseyAwsProxyTest.java | 236 +++++++++--------- .../proxy/jersey/JerseyParamEncodingTest.java | 104 +++++--- 4 files changed, 245 insertions(+), 181 deletions(-) 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 9e27d841c..e8829488f 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 @@ -101,6 +101,21 @@ public AwsProxyRequestBuilder alb() { this.request.getRequestContext().getElb().setTargetGroupArn( "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/d6190d154bc908a5" ); + + // ALB does not decode query string parameters so we re-encode them all + if (request.getMultiValueQueryStringParameters() != null) { + MultiValuedTreeMap newQs = new MultiValuedTreeMap<>(); + for (Map.Entry> e : request.getMultiValueQueryStringParameters().entrySet()) { + for (String v : e.getValue()) { + try { + newQs.add(URLEncoder.encode(e.getKey(), "UTF-8"), URLEncoder.encode(v, "UTF-8")); + } catch (UnsupportedEncodingException ex) { + throw new RuntimeException("Could not encode query string parameters: " + e.getKey() + "=" + v, ex); + } + } + } + request.setMultiValueQueryStringParameters(newQs); + } return this; } 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 83c01a334..bb9d95091 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 @@ -13,17 +13,8 @@ package com.amazonaws.serverless.proxy.jersey; -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.internal.servlet.AwsHttpServletResponse; -import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +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.jersey.suppliers.AwsProxyServletContextSupplier; import com.amazonaws.serverless.proxy.jersey.suppliers.AwsProxyServletRequestSupplier; @@ -31,6 +22,7 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; import com.amazonaws.services.lambda.runtime.Context; import org.glassfish.jersey.internal.inject.AbstractBinder; @@ -42,6 +34,7 @@ import javax.servlet.FilterRegistration; import javax.servlet.Servlet; import javax.servlet.ServletContext; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.Application; @@ -74,7 +67,7 @@ * @param The type for the incoming Lambda event * @param The type for Lambda's return value */ -public class JerseyLambdaContainerHandler extends AwsLambdaServletContainerHandler { +public class JerseyLambdaContainerHandler extends AwsLambdaServletContainerHandler { //------------------------------------------------------------- // Variables - Private @@ -97,13 +90,36 @@ public class JerseyLambdaContainerHandler extends Aws * @return A JerseyLambdaContainerHandler object */ public static JerseyLambdaContainerHandler getAwsProxyHandler(Application jaxRsApplication) { - JerseyLambdaContainerHandler newHandler = new JerseyLambdaContainerHandler<>(AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler(), - jaxRsApplication); + JerseyLambdaContainerHandler newHandler = new JerseyLambdaContainerHandler<>( + AwsProxyRequest.class, + AwsProxyResponse.class, + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + jaxRsApplication); + newHandler.initialize(); + return newHandler; + } + + /** + * Returns an initialized JerseyLambdaContainerHandler that includes RequestReader and + * ResponseWriter objects for the HttpApiV2ProxyRequest and AwsProxyResponse + * objects. + * + * @param jaxRsApplication A configured Jax-Rs application object. For Jersey apps this can be the default + * ResourceConfig object + * @return A JerseyLambdaContainerHandler object + */ + public static JerseyLambdaContainerHandler getHttpApiV2ProxyHandler(Application jaxRsApplication) { + JerseyLambdaContainerHandler newHandler = new JerseyLambdaContainerHandler<>( + HttpApiV2ProxyRequest.class, + AwsProxyResponse.class, + new AwsHttpApiV2HttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsHttpApiV2SecurityContextWriter(), + new AwsProxyExceptionHandler(), + jaxRsApplication); newHandler.initialize(); return newHandler; } @@ -126,7 +142,7 @@ public static JerseyLambdaContainerHandler ge */ public JerseyLambdaContainerHandler(Class requestTypeClass, Class responseTypeClass, - RequestReader requestReader, + RequestReader requestReader, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, @@ -167,12 +183,7 @@ protected void configure() { //------------------------------------------------------------- @Override - protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { - return new AwsHttpServletResponse(request, latch); - } - - @Override - protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext) + protected void handleRequest(HttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext) throws Exception { // we retain the initialized property for backward compatibility if (!initialized) { @@ -180,12 +191,18 @@ protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsH } Timer.start("JERSEY_HANDLE_REQUEST"); - httpServletRequest.setServletContext(getServletContext()); + if (AwsHttpServletRequest.class.isAssignableFrom(httpServletRequest.getClass())) { + ((AwsHttpServletRequest)httpServletRequest).setServletContext(getServletContext()); + } doFilter(httpServletRequest, httpServletResponse, null); Timer.stop("JERSEY_HANDLE_REQUEST"); } + @Override + protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { + return new AwsHttpServletResponse(request, latch); + } @Override public void initialize() { 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 be89c3a06..dbcaac407 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 @@ -22,6 +22,7 @@ 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.model.HttpApiV2ProxyRequest; import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -45,6 +46,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; /** * Unit test class for the Jersey AWS_PROXY default implementation @@ -66,6 +68,13 @@ public class JerseyAwsProxyTest { .register(new ResourceBinder()) .property(LoggingFeature.LOGGING_FEATURE_VERBOSITY_SERVER, LoggingFeature.Verbosity.PAYLOAD_ANY); + private static ResourceConfig httpApiApp = 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) @@ -73,55 +82,71 @@ public class JerseyAwsProxyTest { .register(MultiPartFeature.class) .property(LoggingFeature.LOGGING_FEATURE_VERBOSITY_SERVER, LoggingFeature.Verbosity.PAYLOAD_ANY); - private static JerseyLambdaContainerHandler handler = JerseyLambdaContainerHandler.getAwsProxyHandler(app); + private static JerseyLambdaContainerHandler handler; + private static JerseyLambdaContainerHandler httpApiHandler; private static JerseyLambdaContainerHandler handlerWithoutRegisteredDependencies = JerseyLambdaContainerHandler.getAwsProxyHandler(appWithoutRegisteredDependencies); private static Context lambdaContext = new MockLambdaContext(); - private boolean isAlb; + private String type; - public JerseyAwsProxyTest(boolean alb) { - isAlb = alb; + public JerseyAwsProxyTest(String reqType) { + type = reqType; } @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[] { false, true }); + return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); } private AwsProxyRequestBuilder getRequestBuilder(String path, String method) { - AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder(path, method); - if (isAlb) builder.alb(); - return builder; + return new AwsProxyRequestBuilder(path, method); } - @Test - public void alb_basicRequest_expectSuccess() { - AwsProxyRequest request = getRequestBuilder("/echo/headers", "GET") - .json() - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .alb() - .build(); - - AwsProxyResponse output = handler.proxy(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); - assertNotNull(output.getStatusDescription()); + private AwsProxyResponse executeRequest(AwsProxyRequestBuilder requestBuilder, Context lambdaContext) { + switch (type) { + case "API_GW": + if (handler == null) { + handler = JerseyLambdaContainerHandler.getAwsProxyHandler(app); + } + return handler.proxy(requestBuilder.build(), lambdaContext); + case "ALB": + if (handler == null) { + handler = JerseyLambdaContainerHandler.getAwsProxyHandler(app); + } + return handler.proxy(requestBuilder.alb().build(), lambdaContext); + case "HTTP_API": + if (httpApiHandler == null) { + httpApiHandler = JerseyLambdaContainerHandler.getHttpApiV2ProxyHandler(httpApiApp); + } + return httpApiHandler.proxy(requestBuilder.toHttpApiV2Request(), lambdaContext); + default: + throw new RuntimeException("Unknown request type: " + type); + } + } - validateMapResponseModel(output); + private JerseyLambdaContainerHandler getHandler() { + switch (type) { + case "API_GW": + case "ALB": + return handler; + case "HTTP_API": + return httpApiHandler; + default: + throw new RuntimeException("Unknown request type: " + type); + } } @Test public void headers_getHeaders_echo() { - AwsProxyRequest request = getRequestBuilder("/echo/headers", "GET") + AwsProxyRequestBuilder request = getRequestBuilder("/echo/headers", "GET") .json() - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .build(); + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); @@ -130,12 +155,11 @@ public void headers_getHeaders_echo() { @Test public void headers_servletRequest_echo() { - AwsProxyRequest request = getRequestBuilder("/echo/servlet-headers", "GET") + AwsProxyRequestBuilder request = getRequestBuilder("/echo/servlet-headers", "GET") .json() - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .build(); + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); @@ -144,6 +168,7 @@ public void headers_servletRequest_echo() { @Test public void headers_servletRequest_failedDependencyInjection_expectInternalServerError() { + assumeTrue("API_GW".equals(type)); AwsProxyRequest request = getRequestBuilder("/echo/servlet-headers", "GET") .json() .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) @@ -156,19 +181,18 @@ public void headers_servletRequest_failedDependencyInjection_expectInternalServe @Test public void context_servletResponse_setCustomHeader() { - AwsProxyRequest request = getRequestBuilder("/echo/servlet-response", "GET") - .json() - .build(); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/servlet-response", "GET") + .json(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertTrue(output.getMultiValueHeaders().containsKey(EchoJerseyResource.SERVLET_RESP_HEADER_KEY)); } @Test public void context_serverInfo_correctContext() { - AwsProxyRequest request = getRequestBuilder("/echo/servlet-context", "GET").build(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/servlet-context", "GET"); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); @@ -177,11 +201,10 @@ public void context_serverInfo_correctContext() { @Test public void requestScheme_valid_expectHttps() { - AwsProxyRequest request = getRequestBuilder("/echo/scheme", "GET") - .json() - .build(); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/scheme", "GET") + .json(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); @@ -190,11 +213,10 @@ public void requestScheme_valid_expectHttps() { @Test public void requestFilter_injectsServletRequest_expectCustomAttribute() { - AwsProxyRequest request = getRequestBuilder("/echo/filter-attribute", "GET") - .json() - .build(); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/filter-attribute", "GET") + .json(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); @@ -203,31 +225,27 @@ public void requestFilter_injectsServletRequest_expectCustomAttribute() { @Test public void authorizer_securityContext_customPrincipalSuccess() { - AwsProxyRequest request = getRequestBuilder("/echo/authorizer-principal", "GET") + assumeTrue("API_GW".equals(type)); // TODO: We should figure out a way to run this for HTTP_API too + AwsProxyRequestBuilder request = getRequestBuilder("/echo/authorizer-principal", "GET") .json() - .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID) - .build(); - - AwsProxyResponse output = handler.proxy(request, lambdaContext); - if (!isAlb) { - assertEquals(200, output.getStatusCode()); - assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); - validateSingleValueModel(output, AUTHORIZER_PRINCIPAL_ID); - } - + .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID); + AwsProxyResponse output = executeRequest(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + validateSingleValueModel(output, AUTHORIZER_PRINCIPAL_ID); } @Test public void authorizer_securityContext_customAuthorizerContextSuccess() { - AwsProxyRequest request = getRequestBuilder("/echo/authorizer-context", "GET") + assumeTrue("API_GW".equals(type)); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/authorizer-context", "GET") .json() .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID) .authorizerContextValue(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .queryString("key", CUSTOM_HEADER_KEY) - .build(); + .queryString("key", CUSTOM_HEADER_KEY); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); @@ -236,31 +254,29 @@ public void authorizer_securityContext_customAuthorizerContextSuccess() { @Test public void errors_unknownRoute_expect404() { - AwsProxyRequest request = getRequestBuilder("/echo/test33", "GET").build(); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/test33", "GET"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(404, output.getStatusCode()); } @Test public void error_contentType_invalidContentType() { - AwsProxyRequest request = getRequestBuilder("/echo/json-body", "POST") + AwsProxyRequestBuilder request = getRequestBuilder("/echo/json-body", "POST") .header("Content-Type", "application/octet-stream") - .body("asdasdasd") - .build(); + .body("asdasdasd"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(415, output.getStatusCode()); } @Test public void error_statusCode_methodNotAllowed() { - AwsProxyRequest request = getRequestBuilder("/echo/status-code", "POST") + AwsProxyRequestBuilder request = getRequestBuilder("/echo/status-code", "POST") .json() - .queryString("status", "201") - .build(); + .queryString("status", "201"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(405, output.getStatusCode()); } @@ -268,12 +284,11 @@ public void error_statusCode_methodNotAllowed() { public void responseBody_responseWriter_validBody() throws JsonProcessingException { SingleValueModel singleValueModel = new SingleValueModel(); singleValueModel.setValue(CUSTOM_HEADER_VALUE); - AwsProxyRequest request = getRequestBuilder("/echo/json-body", "POST") + AwsProxyRequestBuilder request = getRequestBuilder("/echo/json-body", "POST") .json() - .body(objectMapper.writeValueAsString(singleValueModel)) - .build(); + .body(objectMapper.writeValueAsString(singleValueModel)); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertNotNull(output.getBody()); @@ -282,29 +297,28 @@ public void responseBody_responseWriter_validBody() throws JsonProcessingExcepti @Test public void statusCode_responseStatusCode_customStatusCode() { - AwsProxyRequest request = getRequestBuilder("/echo/status-code", "GET") + AwsProxyRequestBuilder request = getRequestBuilder("/echo/status-code", "GET") .json() - .queryString("status", "201") - .build(); + .queryString("status", "201"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(201, output.getStatusCode()); } @Test public void base64_binaryResponse_base64Encoding() { - AwsProxyRequest request = getRequestBuilder("/echo/binary", "GET").build(); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/binary", "GET"); - AwsProxyResponse response = handler.proxy(request, lambdaContext); + AwsProxyResponse response = executeRequest(request, lambdaContext); assertNotNull(response.getBody()); assertTrue(Base64.isBase64(response.getBody())); } @Test public void exception_mapException_mapToNotImplemented() { - AwsProxyRequest request = getRequestBuilder("/echo/exception", "GET").build(); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/exception", "GET"); - AwsProxyResponse response = handler.proxy(request, lambdaContext); + AwsProxyResponse response = executeRequest(request, lambdaContext); assertNotNull(response.getBody()); assertEquals(EchoJerseyResource.EXCEPTION_MESSAGE, response.getBody()); assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), response.getStatusCode()); @@ -312,60 +326,58 @@ public void exception_mapException_mapToNotImplemented() { @Test public void stripBasePath_route_shouldRouteCorrectly() { - AwsProxyRequest request = getRequestBuilder("/custompath/echo/status-code", "GET") + AwsProxyRequestBuilder request = getRequestBuilder("/custompath/echo/status-code", "GET") .json() - .queryString("status", "201") - .build(); - handler.stripBasePath("/custompath"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + .queryString("status", "201"); + getHandler().stripBasePath("/custompath"); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(201, output.getStatusCode()); - handler.stripBasePath(""); + getHandler().stripBasePath(""); } @Test public void stripBasePath_route_shouldReturn404WithStageAsContext() { - AwsProxyRequest request = getRequestBuilder("/custompath/echo/status-code", "GET") + assumeTrue(!"ALB".equals(type)); + AwsProxyRequestBuilder request = getRequestBuilder("/custompath/echo/status-code", "GET") .stage("prod") .json() - .queryString("status", "201") - .build(); - handler.stripBasePath("/custompath"); + .queryString("status", "201"); + getHandler().stripBasePath("/custompath"); LambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(true); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(404, output.getStatusCode()); - handler.stripBasePath(""); + getHandler().stripBasePath(""); LambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(false); } @Test public void stripBasePath_route_shouldReturn404() { - AwsProxyRequest request = getRequestBuilder("/custompath/echo/status-code", "GET") + AwsProxyRequestBuilder request = getRequestBuilder("/custompath/echo/status-code", "GET") .json() - .queryString("status", "201") - .build(); - handler.stripBasePath("/custom"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + .queryString("status", "201"); + getHandler().stripBasePath("/custom"); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(404, output.getStatusCode()); - handler.stripBasePath(""); + getHandler().stripBasePath(""); } @Test public void securityContext_injectPrincipal_expectPrincipalName() { - AwsProxyRequest request = getRequestBuilder("/echo/security-context", "GET") - .authorizerPrincipal(USER_PRINCIPAL).build(); + assumeTrue("API_GW".equals(type)); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/security-context", "GET") + .authorizerPrincipal(USER_PRINCIPAL); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, USER_PRINCIPAL); } @Test public void emptyStream_putNullBody_expectPutToSucceed() { - AwsProxyRequest request = getRequestBuilder("/echo/empty-stream/" + CUSTOM_HEADER_KEY + "/test/2", "PUT") + AwsProxyRequestBuilder request = getRequestBuilder("/echo/empty-stream/" + CUSTOM_HEADER_KEY + "/test/2", "PUT") .nullBody() - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .build(); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, CUSTOM_HEADER_KEY); } @@ -373,26 +385,24 @@ public void emptyStream_putNullBody_expectPutToSucceed() { @Test public void refererHeader_headerParam_expectCorrectInjection() { String refererValue = "test-referer"; - AwsProxyRequest request = getRequestBuilder("/echo/referer-header", "GET") + AwsProxyRequestBuilder request = getRequestBuilder("/echo/referer-header", "GET") .nullBody() .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .header("Referer", refererValue) - .build(); + .header("Referer", refererValue); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, refererValue); } @Test public void textPlainContent_plain_responseHonorsContentType() { - AwsProxyRequest req = getRequestBuilder("/echo/plain", "GET") + AwsProxyRequestBuilder req = getRequestBuilder("/echo/plain", "GET") .nullBody() .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN) - .build(); + .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN); - AwsProxyResponse resp = handler.proxy(req, lambdaContext); + AwsProxyResponse resp = executeRequest(req, lambdaContext); assertEquals(200, resp.getStatusCode()); assertTrue(resp.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); assertEquals(MediaType.TEXT_PLAIN, resp.getMultiValueHeaders().get(HttpHeaders.CONTENT_TYPE).get(0)); 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 d3b951aea..e736cbc1e 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 @@ -8,6 +8,7 @@ import com.amazonaws.serverless.proxy.jersey.model.SingleValueModel; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.databind.ObjectMapper; @@ -67,37 +68,62 @@ public class JerseyParamEncodingTest { .register(new ResourceBinder()) .property("jersey.config.server.tracing.type", "ALL") .property("jersey.config.server.tracing.threshold", "VERBOSE"); - private static JerseyLambdaContainerHandler handler = JerseyLambdaContainerHandler.getAwsProxyHandler(app); + private static JerseyLambdaContainerHandler handler; + + private static ResourceConfig httpApiApp = 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 httpApiHandler; private static Context lambdaContext = new MockLambdaContext(); - private boolean isAlb; + private String type; - public JerseyParamEncodingTest(boolean alb) { - isAlb = alb; + public JerseyParamEncodingTest(String reqType) { + type = reqType; LambdaContainerHandler.getContainerConfig().addBinaryContentTypes(MediaType.MULTIPART_FORM_DATA); } @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[] { false, true }); + return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); } private AwsProxyRequestBuilder getRequestBuilder(String path, String method) { - AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder(path, method); - if (isAlb) builder.alb(); + return new AwsProxyRequestBuilder(path, method); + } - return builder; + private AwsProxyResponse executeRequest(AwsProxyRequestBuilder requestBuilder, Context lambdaContext) { + switch (type) { + case "API_GW": + if (handler == null) { + handler = JerseyLambdaContainerHandler.getAwsProxyHandler(app); + } + return handler.proxy(requestBuilder.build(), lambdaContext); + case "ALB": + if (handler == null) { + handler = JerseyLambdaContainerHandler.getAwsProxyHandler(app); + } + return handler.proxy(requestBuilder.alb().build(), lambdaContext); + case "HTTP_API": + if (httpApiHandler == null) { + httpApiHandler = JerseyLambdaContainerHandler.getHttpApiV2ProxyHandler(httpApiApp); + } + return httpApiHandler.proxy(requestBuilder.toHttpApiV2Request(), lambdaContext); + default: + throw new RuntimeException("Unknown request type: " + type); + } } @Test public void queryString_uriInfo_echo() { - AwsProxyRequest request = getRequestBuilder("/echo/query-string", "GET") + AwsProxyRequestBuilder request = getRequestBuilder("/echo/query-string", "GET") .json() - .queryString(QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE) - .build(); + .queryString(QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); @@ -106,12 +132,11 @@ public void queryString_uriInfo_echo() { @Test public void queryString_notEncoded_echo() { - AwsProxyRequest request = getRequestBuilder("/echo/query-string", "GET") + AwsProxyRequestBuilder request = getRequestBuilder("/echo/query-string", "GET") .json() - .queryString(QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE) - .build(); + .queryString(QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); @@ -121,12 +146,11 @@ public void queryString_notEncoded_echo() { @Test @Ignore("We expect to only receive decoded values from API Gateway") public void queryString_encoded_echo() { - AwsProxyRequest request = getRequestBuilder("/echo/query-string", "GET") + AwsProxyRequestBuilder request = getRequestBuilder("/echo/query-string", "GET") .json() - .queryString(QUERY_STRING_KEY, QUERY_STRING_ENCODED_VALUE) - .build(); + .queryString(QUERY_STRING_KEY, QUERY_STRING_ENCODED_VALUE); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); @@ -135,46 +159,46 @@ public void queryString_encoded_echo() { @Test public void simpleQueryParam_encoding_expectDecodedParam() { - AwsProxyRequest request = getRequestBuilder("/echo/decoded-param", "GET").queryString("param", SIMPLE_ENCODED_PARAM).build(); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/decoded-param", "GET").queryString("param", SIMPLE_ENCODED_PARAM); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, SIMPLE_ENCODED_PARAM); } @Test public void jsonQueryParam_encoding_expectDecodedParam() { - AwsProxyRequest request = getRequestBuilder("/echo/decoded-param", "GET").queryString("param", JSON_ENCODED_PARAM).build(); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/decoded-param", "GET").queryString("param", JSON_ENCODED_PARAM); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, JSON_ENCODED_PARAM); } @Test public void simpleQueryParam_encoding_expectEncodedParam() { - AwsProxyRequest request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", SIMPLE_ENCODED_PARAM).build(); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", SIMPLE_ENCODED_PARAM); String encodedVal = ""; try { encodedVal = URLEncoder.encode(SIMPLE_ENCODED_PARAM, "UTF-8"); } catch (UnsupportedEncodingException e) { fail("Could not encode parameter value"); } - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, encodedVal); } @Test public void jsonQueryParam_encoding_expectEncodedParam() { - AwsProxyRequest request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", JSON_ENCODED_PARAM).build(); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", JSON_ENCODED_PARAM); String encodedVal = ""; try { encodedVal = URLEncoder.encode(JSON_ENCODED_PARAM, "UTF-8"); } catch (UnsupportedEncodingException e) { fail("Could not encode parameter value"); } - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, encodedVal); } @@ -182,8 +206,8 @@ public void jsonQueryParam_encoding_expectEncodedParam() { @Test public void queryParam_encoding_expectFullyEncodedUrl() { String paramValue = "/+="; - AwsProxyRequest request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", paramValue).build(); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", paramValue); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertNotNull(resp); assertEquals(resp.getStatusCode(), 200); validateSingleValueModel(resp, "%2F%2B%3D"); @@ -193,8 +217,8 @@ public void queryParam_encoding_expectFullyEncodedUrl() { public void pathParam_encoded_routesToCorrectPath() { String encodedParam = "http%3A%2F%2Fhelloresource.com"; String path = "/echo/encoded-path/" + encodedParam; - AwsProxyRequest request = getRequestBuilder(path, "GET").build(); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyRequestBuilder request = getRequestBuilder(path, "GET"); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertNotNull(resp); assertEquals(resp.getStatusCode(), 200); validateSingleValueModel(resp, encodedParam); @@ -204,8 +228,8 @@ public void pathParam_encoded_routesToCorrectPath() { public void pathParam_encoded_returns404() { String encodedParam = "http://helloresource.com"; String path = "/echo/encoded-path/" + encodedParam; - AwsProxyRequest request = getRequestBuilder(path, "GET").build(); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyRequestBuilder request = getRequestBuilder(path, "GET"); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertNotNull(resp); assertEquals(resp.getStatusCode(), 404); } @@ -213,8 +237,8 @@ public void pathParam_encoded_returns404() { @Test @Ignore public void queryParam_listOfString_expectCorrectLength() { - AwsProxyRequest request = getRequestBuilder("/echo/list-query-string", "GET").queryString("list", "v1,v2,v3").build(); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/list-query-string", "GET").queryString("list", "v1,v2,v3"); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertNotNull(resp); assertEquals(resp.getStatusCode(), 200); validateSingleValueModel(resp, "3"); @@ -223,11 +247,9 @@ public void queryParam_listOfString_expectCorrectLength() { @Test public void multipart_getFileSize_expectCorrectLength() throws IOException { - AwsProxyRequest request = getRequestBuilder("/echo/file-size", "POST") - .formFilePart("file", "myfile.jpg", FILE_CONTENTS) - //.formFieldPart("name", QUERY_STRING_ENCODED_VALUE) - .build(); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyRequestBuilder request = getRequestBuilder("/echo/file-size", "POST") + .formFilePart("file", "myfile.jpg", FILE_CONTENTS); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertNotNull(resp); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, "" + FILE_CONTENTS.length); From b81f0643654cca97a2b90b5a732847d64f099d6c Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 1 Apr 2020 11:31:23 -0700 Subject: [PATCH 26/41] feat: Added HTTP API support to Spark implementation (#329) --- .../testutils/AwsProxyRequestBuilder.java | 10 +++ .../spark/SparkLambdaContainerHandler.java | 46 +++++++++--- .../spark/HelloWorldSparkStreamTest.java | 70 ++++++++++++------- 3 files changed, 89 insertions(+), 37 deletions(-) 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 e8829488f..478955eb2 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 @@ -430,6 +430,16 @@ public InputStream buildStream() { } } + public InputStream toHttpApiV2RequestStream() { + HttpApiV2ProxyRequest req = toHttpApiV2Request(); + try { + String requestJson = LambdaContainerHandler.getObjectMapper().writeValueAsString(req); + return new ByteArrayInputStream(requestJson.getBytes(StandardCharsets.UTF_8)); + } catch (JsonProcessingException e) { + return null; + } + } + public HttpApiV2ProxyRequest toHttpApiV2Request() { HttpApiV2ProxyRequest req = new HttpApiV2ProxyRequest(); req.setRawPath(request.getPath()); 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 368a5e815..2c16eaa98 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 @@ -14,16 +14,12 @@ 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; import com.amazonaws.serverless.proxy.internal.servlet.*; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; import com.amazonaws.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServer; import com.amazonaws.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServerFactory; @@ -38,6 +34,7 @@ import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; import javax.servlet.Servlet; +import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -75,7 +72,7 @@ * @param The response object produced by the ResponseWriter implementation in the constructor */ public class SparkLambdaContainerHandler - extends AwsLambdaServletContainerHandler { + extends AwsLambdaServletContainerHandler { //------------------------------------------------------------- // Constants @@ -121,6 +118,31 @@ public static SparkLambdaContainerHandler get return newHandler; } + /** + * Returns a new instance of an SparkLambdaContainerHandler initialized to work with HttpApiV2ProxyRequest + * and AwsProxyResponse objects. + * + * @return a new instance of SparkLambdaContainerHandler + * + * @throws ContainerInitializationException Throws this exception if we fail to initialize the Spark container. + * This could be caused by the introspection used to insert the library as the default embedded container + */ + public static SparkLambdaContainerHandler getHttpApiV2ProxyHandler() + throws ContainerInitializationException { + SparkLambdaContainerHandler newHandler = new SparkLambdaContainerHandler<>(HttpApiV2ProxyRequest.class, + AwsProxyResponse.class, + new AwsHttpApiV2HttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsHttpApiV2SecurityContextWriter(), + new AwsProxyExceptionHandler(), + new LambdaEmbeddedServerFactory()); + + // For Spark we cannot call initialize here. It needs to be called manually after the routes are set + //newHandler.initialize(); + + return newHandler; + } + //------------------------------------------------------------- // Constructors //------------------------------------------------------------- @@ -128,7 +150,7 @@ public static SparkLambdaContainerHandler get public SparkLambdaContainerHandler(Class requestTypeClass, Class responseTypeClass, - RequestReader requestReader, + RequestReader requestReader, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, @@ -177,13 +199,13 @@ public SparkLambdaContainerHandler(Class requestTypeClass, @Override - protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { return new AwsHttpServletResponse(request, latch); } @Override - protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext) + protected void handleRequest(HttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext) throws Exception { Timer.start("SPARK_HANDLE_REQUEST"); @@ -191,7 +213,9 @@ protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsH initialize(); } - httpServletRequest.setServletContext(getServletContext()); + if (AwsHttpServletRequest.class.isAssignableFrom(httpServletRequest.getClass())) { + ((AwsHttpServletRequest)httpServletRequest).setServletContext(getServletContext()); + } doFilter(httpServletRequest, httpServletResponse, null); Timer.stop("SPARK_HANDLE_REQUEST"); diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkStreamTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkStreamTest.java index 74cd55040..0c41f91f7 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkStreamTest.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkStreamTest.java @@ -8,8 +8,9 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; +import com.amazonaws.services.lambda.runtime.Context; import org.junit.AfterClass; -import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -19,7 +20,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.util.Arrays; import java.util.Collection; @@ -43,36 +43,58 @@ public class HelloWorldSparkStreamTest { private static final String COOKIE_PATH = "/"; private static SparkLambdaContainerHandler handler; + private static SparkLambdaContainerHandler httpApiHandler; - private boolean isAlb; + private String type; - public HelloWorldSparkStreamTest(boolean alb) { - isAlb = alb; + public HelloWorldSparkStreamTest(String reqType) { + type = reqType; + try { + switch (type) { + case "API_GW": + case "ALB": + handler = SparkLambdaContainerHandler.getAwsProxyHandler(); + break; + case "HTTP_API": + httpApiHandler = SparkLambdaContainerHandler.getHttpApiV2ProxyHandler(); + break; + default: + throw new RuntimeException("Unknown request type: " + type); + } + + configureRoutes(); + Spark.awaitInitialization(); + } catch (RuntimeException | ContainerInitializationException e) { + e.printStackTrace(); + fail(); + } } @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[] { false, true }); + return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); } private AwsProxyRequestBuilder getRequestBuilder() { - AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder(); - if (isAlb) builder.alb(); - - return builder; + return new AwsProxyRequestBuilder(); } - @BeforeClass - public static void initializeServer() { - try { - handler = SparkLambdaContainerHandler.getAwsProxyHandler(); - - configureRoutes(); - Spark.awaitInitialization(); - } catch (RuntimeException | ContainerInitializationException e) { - e.printStackTrace(); - fail(); + private ByteArrayOutputStream executeRequest(AwsProxyRequestBuilder requestBuilder, Context lambdaContext) throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + switch (type) { + case "API_GW": + handler.proxyStream(requestBuilder.buildStream(), os, lambdaContext); + break; + case "ALB": + handler.proxyStream(requestBuilder.alb().buildStream(), os, lambdaContext); + break; + case "HTTP_API": + httpApiHandler.proxyStream(requestBuilder.toHttpApiV2RequestStream(), os, lambdaContext); + break; + default: + throw new RuntimeException("Unknown request type: " + type); } + return os; } @AfterClass @@ -82,10 +104,8 @@ public static void stopSpark() { @Test public void helloRequest_basicStream_populatesOutputSuccessfully() { - InputStream req = getRequestBuilder().method("GET").path("/hello").buildStream(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { - handler.proxyStream(req, outputStream, new MockLambdaContext()); + ByteArrayOutputStream outputStream = executeRequest(getRequestBuilder().method("GET").path("/hello"), new MockLambdaContext()); AwsProxyResponse response = LambdaContainerHandler.getObjectMapper().readValue(outputStream.toByteArray(), AwsProxyResponse.class); assertEquals(200, response.getStatusCode()); @@ -100,10 +120,8 @@ public void helloRequest_basicStream_populatesOutputSuccessfully() { @Test public void nullPathRequest_doesntFail_expectA404() { - InputStream req = getRequestBuilder().method("GET").path(null).buildStream(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { - handler.proxyStream(req, outputStream, new MockLambdaContext()); + ByteArrayOutputStream outputStream = executeRequest(getRequestBuilder().method("GET").path(null), new MockLambdaContext()); AwsProxyResponse response = LambdaContainerHandler.getObjectMapper().readValue(outputStream.toByteArray(), AwsProxyResponse.class); assertEquals(404, response.getStatusCode()); From 999e5e0d93e046b35ec9af851ff9ed0749e13a80 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 1 Apr 2020 13:27:33 -0700 Subject: [PATCH 27/41] feat: Added HTTP API support to Spring implementation (#329) --- .../proxy/AsyncInitializationWrapper.java | 4 + .../AwsHttpApiV2ProxyHttpServletRequest.java | 4 +- .../ServletLambdaContainerHandlerBuilder.java | 18 ++ .../testutils/AwsProxyRequestBuilder.java | 15 +- .../proxy/AsyncInitializationWrapperTest.java | 28 ++ aws-serverless-java-container-spring/pom.xml | 9 + .../SpringBootLambdaContainerHandler.java | 35 ++- .../spring/SpringBootProxyHandlerBuilder.java | 26 +- .../spring/SpringLambdaContainerHandler.java | 46 ++-- .../spring/SpringProxyHandlerBuilder.java | 28 +- .../proxy/spring/SpringAwsProxyTest.java | 250 ++++++++++-------- .../proxy/spring/SpringBootAppTest.java | 60 +++-- .../spring/echoapp/EchoSpringAppConfig.java | 44 --- .../spring/springbootapp/LambdaHandler.java | 50 +++- .../springbootslowapp/SBLambdaHandler.java | 2 +- .../spring/springslowapp/LambdaHandler.java | 2 +- 16 files changed, 380 insertions(+), 241 deletions(-) create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AsyncInitializationWrapperTest.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 index 082769cd7..c3b1d6604 100644 --- 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 @@ -75,6 +75,10 @@ public void start(LambdaContainerHandler handler) throws ContainerInitialization } } + public long getActualStartTimeMs() { + return actualStartTime; + } + @Override public CountDownLatch getInitializationLatch() { return initializationLatch; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java index c4148c1bc..c75bc07d8 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java @@ -486,7 +486,9 @@ private Headers headersMapToMultiValue(Map headers) { Headers h = new Headers(); for (Map.Entry hkv : headers.entrySet()) { // Exceptions for known header values that contain commas - if (hkv.getKey().equalsIgnoreCase(HttpHeaders.DATE) || hkv.getKey().toLowerCase(Locale.getDefault()).startsWith("accept-")) { + if (hkv.getKey().equalsIgnoreCase(HttpHeaders.DATE) || + hkv.getKey().equalsIgnoreCase(HttpHeaders.IF_MODIFIED_SINCE) || + hkv.getKey().toLowerCase(Locale.getDefault()).startsWith("accept-")) { h.add(hkv.getKey(), hkv.getValue()); continue; } 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 index ff20e200e..3d6e30d81 100644 --- 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 @@ -4,6 +4,7 @@ import com.amazonaws.serverless.proxy.*; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; @@ -89,6 +90,23 @@ public Builder defaultProxy() { return self(); } + /** + * Sets all of the required fields in the builder to the default settings for a Servlet-compatible framework that wants + * to support HTTP API's v2 proxy event + * @return A populated builder + */ + public Builder defaultHttpApiV2Proxy() { + initializationWrapper(new InitializationWrapper()) + .requestReader((RequestReader) new AwsHttpApiV2HttpServletRequestReader()) + .responseWriter((ResponseWriter) new AwsProxyHttpServletResponseWriter()) + .securityContextWriter((SecurityContextWriter) new AwsHttpApiV2SecurityContextWriter()) + .exceptionHandler((ExceptionHandler) new AwsProxyExceptionHandler()) + .requestTypeClass((Class) HttpApiV2ProxyRequest.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 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 478955eb2..0e3fb816a 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 @@ -108,7 +108,11 @@ public AwsProxyRequestBuilder alb() { for (Map.Entry> e : request.getMultiValueQueryStringParameters().entrySet()) { for (String v : e.getValue()) { try { - newQs.add(URLEncoder.encode(e.getKey(), "UTF-8"), URLEncoder.encode(v, "UTF-8")); + // this is a terrible hack. In our Spring tests we use the comma as a control character for lists + // this is allowed by the HTTP specs although not recommended. + String key = URLEncoder.encode(e.getKey(), "UTF-8").replaceAll("%2C", ","); + String value = URLEncoder.encode(v, "UTF-8").replaceAll("%2C", ","); + newQs.add(key, value); } catch (UnsupportedEncodingException ex) { throw new RuntimeException("Could not encode query string parameters: " + e.getKey() + "=" + v, ex); } @@ -209,6 +213,11 @@ public AwsProxyRequestBuilder multiValueHeaders(Headers h) { return this; } + public AwsProxyRequestBuilder multiValueQueryString(MultiValuedTreeMap params) { + this.request.setMultiValueQueryStringParameters(params); + return this; + } + public AwsProxyRequestBuilder queryString(String key, String value) { if (this.request.getMultiValueQueryStringParameters() == null) { @@ -469,7 +478,9 @@ public HttpApiV2ProxyRequest toHttpApiV2Request() { rawQueryString.append(k); rawQueryString.append("="); try { - rawQueryString.append(URLEncoder.encode(s, "UTF-8")); + // same terrible hack as the alb() method. Because our spring tests use commas as control characters + // we do not encode it + rawQueryString.append(URLEncoder.encode(s, "UTF-8").replaceAll("%2C", ",")); } catch (UnsupportedEncodingException e) { System.out.println("Ex!"); throw new RuntimeException(e); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AsyncInitializationWrapperTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AsyncInitializationWrapperTest.java new file mode 100644 index 000000000..3b8a7306c --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AsyncInitializationWrapperTest.java @@ -0,0 +1,28 @@ +package com.amazonaws.serverless.proxy; + +import org.junit.Test; + +import java.lang.management.ManagementFactory; +import java.time.Clock; +import java.time.Instant; + +import static org.junit.Assert.assertEquals; + +public class AsyncInitializationWrapperTest { + + @Test + public void initCreate_noStartTime_setsCurrentTime() { + AsyncInitializationWrapper init = new AsyncInitializationWrapper(); + long initTime = ManagementFactory.getRuntimeMXBean().getStartTime(); + assertEquals(initTime, init.getActualStartTimeMs()); + } + + @Test + public void initCreate_withStartTime_storesCustomStartTime() throws InterruptedException { + long initTime = Instant.now().toEpochMilli(); + Thread.sleep(500); + AsyncInitializationWrapper init = new AsyncInitializationWrapper(initTime); + + assertEquals(initTime, init.getActualStartTimeMs()); + } +} diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index 5b6117cb9..6065d464f 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -60,6 +60,15 @@ test + + + javax.activation + activation + 1.1.1 + test + + + org.hibernate hibernate-validator 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 6f9fe9f60..98841ed44 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 @@ -18,6 +18,7 @@ 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.model.HttpApiV2ProxyRequest; import com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory; import com.amazonaws.services.lambda.runtime.Context; import org.slf4j.Logger; @@ -30,6 +31,7 @@ import org.springframework.web.context.ConfigurableWebApplicationContext; import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; import java.util.concurrent.CountDownLatch; @@ -49,7 +51,7 @@ * @param The expected return type */ @Deprecated -public class SpringBootLambdaContainerHandler extends AwsLambdaServletContainerHandler { +public class SpringBootLambdaContainerHandler extends AwsLambdaServletContainerHandler { private static final String DISPATCHER_SERVLET_REGISTRATION_NAME = "dispatcherServlet"; private final Class springBootInitializer; @@ -82,7 +84,7 @@ public static SpringBootLambdaContainerHandler getInstance() { */ public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class springBootInitializer, String... profiles) throws ContainerInitializationException { - return new SpringBootProxyHandlerBuilder() + return new SpringBootProxyHandlerBuilder() .defaultProxy() .initializationWrapper(new InitializationWrapper()) .springBootApplication(springBootInitializer) @@ -90,6 +92,23 @@ public static SpringBootLambdaContainerHandler getHttpApiV2ProxyHandler(Class springBootInitializer, String... profiles) + throws ContainerInitializationException { + return new SpringBootProxyHandlerBuilder() + .defaultHttpApiV2Proxy() + .initializationWrapper(new InitializationWrapper()) + .springBootApplication(springBootInitializer) + .profiles(profiles) + .buildAndInitialize(); + } + /** * Creates a new container handler with the given reader and writer objects * @@ -103,7 +122,7 @@ public static SpringBootLambdaContainerHandler requestTypeClass, Class responseTypeClass, - RequestReader requestReader, + RequestReader requestReader, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, @@ -134,12 +153,12 @@ public void activateSpringProfiles(String... profiles) { } @Override - protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { return new AwsHttpServletResponse(request, latch); } @Override - protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + protected void handleRequest(HttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { // this method of the AwsLambdaServletContainerHandler sets the servlet context Timer.start("SPRINGBOOT_HANDLE_REQUEST"); @@ -148,11 +167,13 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt initialize(); } - containerRequest.setServletContext(getServletContext()); + if (AwsHttpServletRequest.class.isAssignableFrom(containerRequest.getClass())) { + ((AwsHttpServletRequest)containerRequest).setServletContext(getServletContext()); + ((AwsHttpServletRequest)containerRequest).setResponse(containerResponse); + } // 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/SpringBootProxyHandlerBuilder.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java index 4ead5bc4d..edc76148d 100644 --- 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 @@ -7,38 +7,40 @@ import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import org.springframework.web.WebApplicationInitializer; -public final class SpringBootProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< - AwsProxyRequest, +import javax.servlet.http.HttpServletRequest; + +public final class SpringBootProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< + RequestType, AwsProxyResponse, - AwsProxyHttpServletRequest, - SpringBootLambdaContainerHandler, - SpringBootProxyHandlerBuilder> { + HttpServletRequest, + SpringBootLambdaContainerHandler, + SpringBootProxyHandlerBuilder> { private Class springBootInitializer; private String[] profiles; @Override - protected SpringBootProxyHandlerBuilder self() { + protected SpringBootProxyHandlerBuilder self() { return this; } - public SpringBootProxyHandlerBuilder springBootApplication(Class app) { + public SpringBootProxyHandlerBuilder springBootApplication(Class app) { springBootInitializer = app; return self(); } - public SpringBootProxyHandlerBuilder profiles(String... profiles) { + public SpringBootProxyHandlerBuilder profiles(String... profiles) { this.profiles = profiles; return self(); } @Override - public SpringBootLambdaContainerHandler build() throws ContainerInitializationException { + public SpringBootLambdaContainerHandler build() throws ContainerInitializationException { validate(); if (springBootInitializer == null) { throw new ContainerInitializationException("Missing spring boot application class in builder", null); } - SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler<>( + SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler( requestTypeClass, responseTypeClass, requestReader, @@ -55,8 +57,8 @@ public SpringBootLambdaContainerHandler build } @Override - public SpringBootLambdaContainerHandler buildAndInitialize() throws ContainerInitializationException { - SpringBootLambdaContainerHandler handler = build(); + 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 dd3f90c93..862df3bdf 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 @@ -18,14 +18,14 @@ 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.model.HttpApiV2ProxyRequest; 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 javax.servlet.http.HttpServletRequest; import java.util.concurrent.CountDownLatch; @@ -36,7 +36,7 @@ * @param The incoming event type * @param The expected return type */ -public class SpringLambdaContainerHandler extends AwsLambdaServletContainerHandler { +public class SpringLambdaContainerHandler extends AwsLambdaServletContainerHandler { private ConfigurableWebApplicationContext appContext; private String[] profiles; @@ -47,10 +47,10 @@ public class SpringLambdaContainerHandler extends Aws * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects * @param config A set of classes annotated with the Spring @Configuration annotation * @return An initialized instance of the `SpringLambdaContainerHandler` - * @throws ContainerInitializationException + * @throws ContainerInitializationException When the Spring framework fails to start. */ - public static SpringLambdaContainerHandler getAwsProxyHandler(Class... config) throws ContainerInitializationException { - return new SpringProxyHandlerBuilder() + public static SpringLambdaContainerHandler getAwsProxyHandler(Class... config) throws ContainerInitializationException { + return new SpringProxyHandlerBuilder() .defaultProxy() .initializationWrapper(new InitializationWrapper()) .configurationClasses(config) @@ -62,11 +62,11 @@ public static SpringLambdaContainerHandler ge * @param applicationContext A custom ConfigurableWebApplicationContext to be used * @param profiles The spring profiles to activate * @return An initialized instance of the `SpringLambdaContainerHandler` - * @throws ContainerInitializationException + * @throws ContainerInitializationException When the Spring framework fails to start. */ public static SpringLambdaContainerHandler getAwsProxyHandler(ConfigurableWebApplicationContext applicationContext, String... profiles) throws ContainerInitializationException { - return new SpringProxyHandlerBuilder() + return new SpringProxyHandlerBuilder() .defaultProxy() .initializationWrapper(new InitializationWrapper()) .springApplicationContext(applicationContext) @@ -74,6 +74,20 @@ public static SpringLambdaContainerHandler ge .buildAndInitialize(); } + /** + * Creates a default SpringLambdaContainerHandler initialized with the `HttpApiV2ProxyRequest` and `AwsProxyResponse` objects + * @param config A set of classes annotated with the Spring @Configuration annotation + * @return An initialized instance of the `SpringLambdaContainerHandler` + * @throws ContainerInitializationException When the Spring framework fails to start. + */ + public static SpringLambdaContainerHandler getHttpApiV2ProxyHandler(Class... config) throws ContainerInitializationException { + return new SpringProxyHandlerBuilder() + .defaultHttpApiV2Proxy() + .initializationWrapper(new InitializationWrapper()) + .configurationClasses(config) + .buildAndInitialize(); + } + /** * Creates a new container handler with the given reader and writer objects * @@ -82,17 +96,15 @@ public static SpringLambdaContainerHandler ge * @param responseWriter An implementation of `ResponseWriter` * @param securityContextWriter An implementation of `SecurityContextWriter` * @param exceptionHandler An implementation of `ExceptionHandler` - * @throws ContainerInitializationException */ public SpringLambdaContainerHandler(Class requestTypeClass, Class responseTypeClass, - RequestReader requestReader, + RequestReader requestReader, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, ConfigurableWebApplicationContext applicationContext, - InitializationWrapper init) - throws ContainerInitializationException { + InitializationWrapper init) { super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); Timer.start("SPRING_CONTAINER_HANDLER_CONSTRUCTOR"); appContext = applicationContext; @@ -112,7 +124,7 @@ public void setRefreshContext(boolean refresh) { @Override - protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { return new AwsHttpServletResponse(request, latch); } @@ -132,7 +144,7 @@ public void activateSpringProfiles(String... p) throws ContainerInitializationEx } @Override - protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + protected void handleRequest(HttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { Timer.start("SPRING_HANDLE_REQUEST"); if (refreshContext) { @@ -140,11 +152,13 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt refreshContext = false; } - containerRequest.setServletContext(getServletContext()); + if (AwsHttpServletRequest.class.isAssignableFrom(containerRequest.getClass())) { + ((AwsHttpServletRequest)containerRequest).setServletContext(getServletContext()); + ((AwsHttpServletRequest)containerRequest).setResponse(containerResponse); + } // 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-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 index 9bfbf3cbc..8af0efbca 100644 --- 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 @@ -9,39 +9,41 @@ import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -public final class SpringProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< - AwsProxyRequest, +import javax.servlet.http.HttpServletRequest; + +public final class SpringProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< + RequestType, AwsProxyResponse, - AwsProxyHttpServletRequest, - SpringLambdaContainerHandler, - SpringProxyHandlerBuilder> { + HttpServletRequest, + SpringLambdaContainerHandler, + SpringProxyHandlerBuilder> { private ConfigurableWebApplicationContext springContext; private Class[] configurationClasses; private String[] profiles; @Override - protected SpringProxyHandlerBuilder self() { + protected SpringProxyHandlerBuilder self() { return this; } - public SpringProxyHandlerBuilder springApplicationContext(ConfigurableWebApplicationContext app) { + public SpringProxyHandlerBuilder springApplicationContext(ConfigurableWebApplicationContext app) { springContext = app; return self(); } - public SpringProxyHandlerBuilder configurationClasses(Class... config) { + public SpringProxyHandlerBuilder configurationClasses(Class... config) { configurationClasses = config; return self(); } - public SpringProxyHandlerBuilder profiles(String... profiles) { + public SpringProxyHandlerBuilder profiles(String... profiles) { this.profiles = profiles; return self(); } @Override - public SpringLambdaContainerHandler build() throws ContainerInitializationException { + 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" + @@ -55,7 +57,7 @@ public SpringLambdaContainerHandler build() t } } - SpringLambdaContainerHandler handler = new SpringLambdaContainerHandler<>( + SpringLambdaContainerHandler handler = new SpringLambdaContainerHandler( requestTypeClass, responseTypeClass, requestReader, @@ -72,8 +74,8 @@ public SpringLambdaContainerHandler build() t } @Override - public SpringLambdaContainerHandler buildAndInitialize() throws ContainerInitializationException { - SpringLambdaContainerHandler handler = build(); + public SpringLambdaContainerHandler buildAndInitialize() throws ContainerInitializationException { + SpringLambdaContainerHandler handler = build(); initializationWrapper.start(handler); return handler; } 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 cbb9a3a15..c1365a5b1 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 @@ -1,24 +1,27 @@ package com.amazonaws.serverless.proxy.spring; +import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; +import com.amazonaws.serverless.proxy.internal.servlet.AwsServletRegistration; +import com.amazonaws.serverless.proxy.model.*; import com.amazonaws.serverless.proxy.internal.servlet.AwsServletContext; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; -import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.serverless.proxy.spring.echoapp.EchoResource; import com.amazonaws.serverless.proxy.spring.echoapp.EchoSpringAppConfig; import com.amazonaws.serverless.proxy.spring.echoapp.RestControllerAdvice; import com.amazonaws.serverless.proxy.spring.echoapp.UnauthenticatedFilter; import com.amazonaws.serverless.proxy.spring.echoapp.model.MapResponseModel; import com.amazonaws.serverless.proxy.spring.echoapp.model.SingleValueModel; +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; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; @@ -26,36 +29,88 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.servlet.DispatcherServlet; +import javax.servlet.DispatcherType; +import javax.servlet.FilterRegistration; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import java.io.IOException; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; import java.util.UUID; import static org.junit.Assert.*; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = {EchoSpringAppConfig.class}) -@WebAppConfiguration -@TestExecutionListeners(inheritListeners = false, listeners = {DependencyInjectionTestExecutionListener.class}) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@RunWith(Parameterized.class) public class SpringAwsProxyTest { private static final String CUSTOM_HEADER_KEY = "x-custom-header"; private static final String CUSTOM_HEADER_VALUE = "my-custom-value"; private static final String AUTHORIZER_PRINCIPAL_ID = "test-principal-" + UUID.randomUUID().toString(); private static final String UNICODE_VALUE = "שלום לכולם"; - @Autowired - private ObjectMapper objectMapper; + private ObjectMapper objectMapper = new ObjectMapper(); + private MockLambdaContext lambdaContext = new MockLambdaContext(); + private static SpringLambdaContainerHandler handler; + private static SpringLambdaContainerHandler httpApiHandler; - @Autowired - private MockLambdaContext lambdaContext; + private AwsLambdaServletContainerHandler.StartupHandler h = (c -> { + FilterRegistration.Dynamic registration = c.addFilter("UnauthenticatedFilter", UnauthenticatedFilter.class); + // update the registration to map to a path + registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/echo/*"); + // servlet name mappings are disabled and will throw an exception - @Autowired - private SpringLambdaContainerHandler handler; + //handler.getApplicationInitializer().getDispatcherServlet().setThrowExceptionIfNoHandlerFound(true); + ((DispatcherServlet)((AwsServletRegistration)c.getServletRegistration("dispatcherServlet")).getServlet()).setThrowExceptionIfNoHandlerFound(true); + }); + + private String type; + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); + } + + public SpringAwsProxyTest(String reqType) { + type = reqType; + } + + private AwsProxyResponse executeRequest(AwsProxyRequestBuilder requestBuilder, Context lambdaContext) { + try { + switch (type) { + case "API_GW": + if (handler == null) { + handler = SpringLambdaContainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class); + handler.onStartup(h); + } + return handler.proxy(requestBuilder.build(), lambdaContext); + case "ALB": + if (handler == null) { + handler = SpringLambdaContainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class); + handler.onStartup(h); + } + return handler.proxy(requestBuilder.alb().build(), lambdaContext); + case "HTTP_API": + if (httpApiHandler == null) { + httpApiHandler = SpringLambdaContainerHandler.getHttpApiV2ProxyHandler(EchoSpringAppConfig.class); + httpApiHandler.onStartup(h); + } + return httpApiHandler.proxy(requestBuilder.toHttpApiV2Request(), lambdaContext); + default: + throw new RuntimeException("Unknown request type: " + type); + } + } catch (ContainerInitializationException e) { + e.printStackTrace(); + fail("Could not execute request"); + throw new RuntimeException(e); + } + } @Before public void clearServletContextCache() { @@ -64,12 +119,11 @@ public void clearServletContextCache() { @Test public void controllerAdvice_invalidPath_returnAdvice() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo2", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo2", "GET") .json() - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .build(); + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertNotNull(output); assertEquals(404, output.getStatusCode()); validateSingleValueModel(output, RestControllerAdvice.ERROR_MESSAGE); @@ -78,12 +132,11 @@ public void controllerAdvice_invalidPath_returnAdvice() { @Test public void headers_getHeaders_echo() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/headers", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/headers", "GET") .json() - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .build(); + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type").split(";")[0]); @@ -92,12 +145,11 @@ public void headers_getHeaders_echo() { @Test public void headers_servletRequest_echo() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/servlet-headers", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/servlet-headers", "GET") .json() - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .build(); + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type").split(";")[0]); @@ -106,12 +158,11 @@ public void headers_servletRequest_echo() { @Test public void queryString_uriInfo_echo() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/query-string", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/query-string", "GET") .json() - .queryString(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .build(); + .queryString(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type").split(";")[0]); @@ -120,12 +171,11 @@ public void queryString_uriInfo_echo() { @Test public void queryString_listParameter_expectCorrectLength() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/list-query-string", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/list-query-string", "GET") .json() - .queryString("list", "v1,v2,v3") - .build(); + .queryString("list", "v1,v2,v3"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); validateSingleValueModel(output, "3"); @@ -134,13 +184,12 @@ public void queryString_listParameter_expectCorrectLength() { @Test public void queryString_multiParam_expectCorrectValueCount() throws IOException { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/multivalue-query-string", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/multivalue-query-string", "GET") .json() .queryString("multiple", "first") - .queryString("multiple", "second") - .build(); + .queryString("multiple", "second"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); MapResponseModel response = objectMapper.readValue(output.getBody(), MapResponseModel.class); @@ -151,42 +200,40 @@ public void queryString_multiParam_expectCorrectValueCount() @Test public void dateHeader_notModified_expect304() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/last-modified", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/last-modified", "GET") .json() .header( HttpHeaders.IF_MODIFIED_SINCE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now().minus(1, ChronoUnit.SECONDS)) - ) - .build(); + ); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(304, output.getStatusCode()); assertEquals("", output.getBody()); } @Test public void dateHeader_notModified_expect200() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/last-modified", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/last-modified", "GET") .json() .header( HttpHeaders.IF_MODIFIED_SINCE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now().minus(5, ChronoUnit.DAYS)) - ) - .build(); + ); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals(EchoResource.STRING_BODY, output.getBody()); } @Test public void authorizer_securityContext_customPrincipalSuccess() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/authorizer-principal", "GET") + assumeTrue("API_GW".equals(type)); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/authorizer-principal", "GET") .json() - .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID) - .build(); + .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type").split(";")[0]); @@ -195,43 +242,40 @@ public void authorizer_securityContext_customPrincipalSuccess() { @Test public void errors_unknownRoute_expect404() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/test33", "GET").build(); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/test33", "GET"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(404, output.getStatusCode()); } @Test public void error_contentType_invalidContentType() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/json-body", "POST") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/json-body", "POST") .header("Content-Type", "application/octet-stream") - .body("asdasdasd") - .build(); + .body("asdasdasd"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(415, output.getStatusCode()); } @Test public void error_statusCode_methodNotAllowed() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/status-code", "POST") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/status-code", "POST") .json() - .queryString("status", "201") - .build(); + .queryString("status", "201"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(405, output.getStatusCode()); } @Test public void error_unauthenticatedCall_filterStepsRequest() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/status-code", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/status-code", "GET") .header(UnauthenticatedFilter.HEADER_NAME, "1") .json() - .queryString("status", "201") - .build(); + .queryString("status", "201"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(401, output.getStatusCode()); } @@ -239,13 +283,12 @@ public void error_unauthenticatedCall_filterStepsRequest() { public void responseBody_responseWriter_validBody() throws JsonProcessingException { SingleValueModel singleValueModel = new SingleValueModel(); singleValueModel.setValue(CUSTOM_HEADER_VALUE); - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/json-body", "POST") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/json-body", "POST") .json() .header("Content-Type", "application/json") - .body(objectMapper.writeValueAsString(singleValueModel)) - .build(); + .body(objectMapper.writeValueAsString(singleValueModel)); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertNotNull(output.getBody()); validateSingleValueModel(output, CUSTOM_HEADER_VALUE); @@ -255,12 +298,11 @@ public void responseBody_responseWriter_validBody() throws JsonProcessingExcepti public void responseBody_responseWriter_validBody_UTF() throws JsonProcessingException { SingleValueModel singleValueModel = new SingleValueModel(); singleValueModel.setValue(UNICODE_VALUE); - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/json-body", "POST") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/json-body", "POST") .header("Content-Type", "application/json; charset=UTF-8") - .body(objectMapper.writeValueAsString(singleValueModel)) - .build(); + .body(objectMapper.writeValueAsString(singleValueModel)); LambdaContainerHandler.getContainerConfig().setDefaultContentCharset("UTF-8"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertNotNull(output.getBody()); validateSingleValueModel(output, UNICODE_VALUE); @@ -269,32 +311,30 @@ public void responseBody_responseWriter_validBody_UTF() throws JsonProcessingExc @Test public void statusCode_responseStatusCode_customStatusCode() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/status-code", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/status-code", "GET") .json() - .queryString("status", "201") - .build(); + .queryString("status", "201"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(201, output.getStatusCode()); } @Test public void base64_binaryResponse_base64Encoding() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/binary", "GET").build(); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/binary", "GET"); - AwsProxyResponse response = handler.proxy(request, lambdaContext); + AwsProxyResponse response = executeRequest(request, lambdaContext); assertNotNull(response.getBody()); assertTrue(Base64.isBase64(response.getBody())); } @Test public void injectBody_populatedResponse_noException() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/request-body", "POST") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/request-body", "POST") .header(HttpHeaders.CONTENT_TYPE, "text/plain") - .body("This is a populated body") - .build(); + .body("This is a populated body"); - AwsProxyResponse response = handler.proxy(request, lambdaContext); + AwsProxyResponse response = executeRequest(request, lambdaContext); assertNotNull(response.getBody()); assertEquals(200, response.getStatusCode()); try { @@ -305,9 +345,8 @@ public void injectBody_populatedResponse_noException() { fail(); } - AwsProxyRequest emptyReq = new AwsProxyRequestBuilder("/echo/request-body", "POST") - .build(); - AwsProxyResponse emptyResp = handler.proxy(emptyReq, lambdaContext); + AwsProxyRequestBuilder emptyReq = new AwsProxyRequestBuilder("/echo/request-body", "POST"); + AwsProxyResponse emptyResp = executeRequest(emptyReq, lambdaContext); try { SingleValueModel output = objectMapper.readValue(emptyResp.getBody(), SingleValueModel.class); assertEquals(null, output.getValue()); @@ -321,28 +360,26 @@ public void injectBody_populatedResponse_noException() { public void servletRequestEncoding_acceptEncoding_okStatusCode() { SingleValueModel singleValueModel = new SingleValueModel(); singleValueModel.setValue(CUSTOM_HEADER_VALUE); - AwsProxyRequest request = null; + AwsProxyRequestBuilder request = null; try { request = new AwsProxyRequestBuilder("/echo/json-body", "POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .header(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate") .queryString("status", "200") - .body(objectMapper.writeValueAsString(singleValueModel)) - .build(); + .body(objectMapper.writeValueAsString(singleValueModel)); } catch (JsonProcessingException e) { fail("Could not serialize object to JSON"); } - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); } @Test public void request_requestURI() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/request-URI", "GET") - .build(); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/request-URI", "GET"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); validateSingleValueModel(output, "/echo/request-URI"); @@ -350,13 +387,12 @@ public void request_requestURI() { @Test public void request_requestURL() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/request-url", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/request-url", "GET") .scheme("https") .serverName("api.myserver.com") - .stage("prod") - .build(); + .stage("prod"); handler.stripBasePath(""); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); validateSingleValueModel(output, "https://api.myserver.com/echo/request-url"); @@ -364,13 +400,12 @@ public void request_requestURL() { @Test public void request_encodedPath_returnsDecodedPath() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/encoded-request-uri/Some%20Thing", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/encoded-request-uri/Some%20Thing", "GET") .scheme("https") .serverName("api.myserver.com") - .stage("prod") - .build(); + .stage("prod"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); validateSingleValueModel(output, "Some Thing"); @@ -379,15 +414,15 @@ public void request_encodedPath_returnsDecodedPath() { @Test public void contextPath_generateLink_returnsCorrectPath() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/generate-uri", "GET") + assumeFalse("ALB".equals(type)); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/generate-uri", "GET") .scheme("https") .serverName("api.myserver.com") - .stage("prod") - .build(); + .stage("prod"); LambdaContainerHandler.getContainerConfig().addCustomDomain("api.myserver.com"); SpringLambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(true); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); String expectedUri = "https://api.myserver.com/prod/echo/encoded-request-uri/" + EchoResource.TEST_GENERATE_URI; @@ -400,11 +435,10 @@ public void contextPath_generateLink_returnsCorrectPath() { @Test public void multipart_getFileName_returnsCorrectFileName() throws IOException { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/attachment", "POST") - .formFilePart("testFile", "myFile.txt", "hello".getBytes()) - .build(); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/attachment", "POST") + .formFilePart("testFile", "myFile.txt", "hello".getBytes()); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); 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 2e029e921..2a345b6ff 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 @@ -15,10 +15,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.springframework.core.SpringVersion; import javax.ws.rs.core.HttpHeaders; import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; import java.util.Objects; import static com.amazonaws.serverless.proxy.spring.springbootapp.TestController.CUSTOM_HEADER_NAME; @@ -27,11 +31,19 @@ import static org.junit.Assume.assumeFalse; +@RunWith(Parameterized.class) public class SpringBootAppTest { - private LambdaHandler handler = new LambdaHandler(); + private LambdaHandler handler; private MockLambdaContext context = new MockLambdaContext(); private ObjectMapper mapper = new ObjectMapper(); + private String type; + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); + } + @BeforeClass public static void before() { // We skip the tests if we are running against Spring 5.2.x - SpringBoot 1.5 is deprecated and no longer @@ -40,9 +52,14 @@ public static void before() { assumeFalse(Objects.requireNonNull(SpringVersion.getVersion()).startsWith("5.2")); } + public SpringBootAppTest(String reqType) { + type = reqType; + handler = new LambdaHandler(type); + } + @Test public void testMethod_springSecurity_doesNotThrowException() { - AwsProxyRequest req = new AwsProxyRequestBuilder("/test", "GET").build(); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/test", "GET"); AwsProxyResponse resp = handler.handleRequest(req, context); assertNotNull(resp); assertEquals(200, resp.getStatusCode()); @@ -51,7 +68,7 @@ public void testMethod_springSecurity_doesNotThrowException() { @Test public void testMethod_testRequestFromString_doesNotThrowNpe() throws IOException { - AwsProxyRequest req = new AwsProxyRequestBuilder().fromJsonString("{\n" + + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder().fromJsonString("{\n" + " \"resource\": \"/missing-params\",\n" + " \"path\": \"/missing-params\",\n" + " \"httpMethod\": \"GET\",\n" + @@ -81,25 +98,25 @@ public void testMethod_testRequestFromString_doesNotThrowNpe() throws IOExceptio " },\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"); + req.multiValueHeaders(new Headers()); + req.header(CUSTOM_HEADER_NAME, "val"); resp = handler.handleRequest(req, context); assertEquals(400, resp.getStatusCode()); - req.setMultiValueQueryStringParameters(new MultiValuedTreeMap<>()); - req.getMultiValueQueryStringParameters().add(CUSTOM_QS_NAME, "val"); + req.multiValueQueryString(new MultiValuedTreeMap<>()); + req.queryString(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(); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/test2", "GET"); AwsProxyResponse resp = handler.handleRequest(req, context); assertNotNull(resp); assertEquals(404, resp.getStatusCode()); @@ -108,7 +125,7 @@ public void defaultError_requestForward_springBootForwardsToDefaultErrorPage() { @Test public void requestUri_dotInPathParam_expectRoutingToMethod() { - AwsProxyRequest req = new AwsProxyRequestBuilder("/test/testdomain.com", "GET").build(); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/test/testdomain.com", "GET"); AwsProxyResponse resp = handler.handleRequest(req, context); assertNotNull(resp); @@ -118,8 +135,8 @@ public void requestUri_dotInPathParam_expectRoutingToMethod() { @Test public void queryString_commaSeparatedList_expectUnmarshalAsList() { - AwsProxyRequest req = new AwsProxyRequestBuilder("/test/query-string", "GET") - .queryString("list", "v1,v2,v3").build(); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/test/query-string", "GET") + .queryString("list", "v1,v2,v3"); AwsProxyResponse resp = handler.handleRequest(req, context); assertNotNull(resp); assertEquals(200, resp.getStatusCode()); @@ -128,8 +145,8 @@ public void queryString_commaSeparatedList_expectUnmarshalAsList() { @Test public void queryString_multipleParamsWithSameName_expectUnmarshalAsList() { - AwsProxyRequest req = new AwsProxyRequestBuilder("/test/query-string", "GET") - .queryString("list", "v1").queryString("list", "v2").build(); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/test/query-string", "GET") + .queryString("list", "v1").queryString("list", "v2"); AwsProxyResponse resp = handler.handleRequest(req, context); assertNotNull(resp); assertEquals(200, resp.getStatusCode()); @@ -138,11 +155,10 @@ public void queryString_multipleParamsWithSameName_expectUnmarshalAsList() { @Test public void staticContent_getHtmlFile_returnsHtmlContent() { - LambdaContainerHandler.getContainerConfig().addValidFilePath("/Users/bulianis/workspace/aws-serverless-java-container/aws-serverless-java-container-spring"); - AwsProxyRequest request = new AwsProxyRequestBuilder("/static.html", "GET") + LambdaContainerHandler.getContainerConfig().addValidFilePath(System.getProperty("user.dir")); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/static.html", "GET") .header(HttpHeaders.ACCEPT, "text/html") - .header(HttpHeaders.CONTENT_TYPE, "text/plain") - .build(); + .header(HttpHeaders.CONTENT_TYPE, "text/plain"); AwsProxyResponse output = handler.handleRequest(request, context); assertEquals(200, output.getStatusCode()); assertTrue(output.getBody().contains("

Static

")); @@ -151,8 +167,7 @@ public void staticContent_getHtmlFile_returnsHtmlContent() { @Test public void utf8_returnUtf8String_expectCorrectHeaderMediaAndCharset() { LambdaContainerHandler.getContainerConfig().setDefaultContentCharset("UTF-8"); - AwsProxyRequest request = new AwsProxyRequestBuilder("/test/utf8", "GET") - .build(); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/test/utf8", "GET"); AwsProxyResponse output = handler.handleRequest(request, context); validateSingleValueModel(output, TestController.UTF8_TEST_STRING); assertTrue(output.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); @@ -163,9 +178,8 @@ public void utf8_returnUtf8String_expectCorrectHeaderMediaAndCharset() { @Test public void utf8_returnUtf8String_expectCorrectHeaderMediaAndCharsetNoDefault() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/test/utf8", "GET") - .header("Content-Type", "application/json; charset=UTF-8") - .build(); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/test/utf8", "GET") + .header("Content-Type", "application/json; charset=UTF-8"); AwsProxyResponse output = handler.handleRequest(request, context); validateSingleValueModel(output, TestController.UTF8_TEST_STRING); assertTrue(output.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); 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 6602e9b97..a4d5f069c 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 @@ -1,62 +1,18 @@ package com.amazonaws.serverless.proxy.spring.echoapp; -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; 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.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; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; 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; @Configuration @ComponentScan("com.amazonaws.serverless.proxy.spring.echoapp") @PropertySource("classpath:application.properties") public class EchoSpringAppConfig { - - @Autowired - private ConfigurableWebApplicationContext applicationContext; - - @Bean - public SpringLambdaContainerHandler springLambdaContainerHandler() throws ContainerInitializationException { - 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 - registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/echo/*"); - // servlet name mappings are disabled and will throw an exception - - //handler.getApplicationInitializer().getDispatcherServlet().setThrowExceptionIfNoHandlerFound(true); - ((DispatcherServlet)((AwsServletRegistration)c.getServletRegistration("dispatcherServlet")).getServlet()).setThrowExceptionIfNoHandlerFound(true); - - }); - return handler; - } - @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/LambdaHandler.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/LambdaHandler.java index 4ca9c4b2c..aee0bd035 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/LambdaHandler.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/LambdaHandler.java @@ -2,8 +2,10 @@ import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; import com.amazonaws.serverless.proxy.spring.springbootapp.TestApplication; import com.amazonaws.services.lambda.runtime.Context; @@ -11,24 +13,46 @@ public class LambdaHandler - implements RequestHandler + implements RequestHandler { SpringBootLambdaContainerHandler handler; - boolean isinitialized = false; + SpringBootLambdaContainerHandler httpApiHandler; + private String type; - public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) + public LambdaHandler(String reqType) { + type = reqType; + switch (type) { + case "API_GW": + case "ALB": + try { + handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(TestApplication.class); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + break; + case "HTTP_API": + try { + httpApiHandler = SpringBootLambdaContainerHandler.getHttpApiV2ProxyHandler(TestApplication.class); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + } + + public AwsProxyResponse handleRequest(AwsProxyRequestBuilder awsProxyRequest, Context context) { - if (!isinitialized) { - isinitialized = true; - try { - handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(TestApplication.class); - } catch (ContainerInitializationException e) { - e.printStackTrace(); - return null; - } + switch (type) { + case "API_GW": + return handler.proxy(awsProxyRequest.build(), context); + case "ALB": + return handler.proxy(awsProxyRequest.alb().build(), context); + case "HTTP_API": + return httpApiHandler.proxy(awsProxyRequest.toHttpApiV2Request(), context); + default: + throw new RuntimeException("Unknown request type: " + type); } - AwsProxyResponse res = handler.proxy(awsProxyRequest, context); - return res; } } 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 index cd1e09bbc..157b6c438 100644 --- 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 @@ -20,7 +20,7 @@ public class SBLambdaHandler public SBLambdaHandler() throws ContainerInitializationException { long startTime = Instant.now().toEpochMilli(); - handler = new SpringBootProxyHandlerBuilder() + handler = new SpringBootProxyHandlerBuilder() .defaultProxy() .asyncInit(Instant.now().toEpochMilli()) .springBootApplication(TestApplication.class) 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 index 7eaf54039..935954d0f 100644 --- 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 @@ -16,7 +16,7 @@ public class LambdaHandler implements RequestHandler() .defaultProxy() .asyncInit() .configurationClasses(SlowAppConfig.class) From 7de1865f9c1e2a1a06933dc9c68e5311a85b3498 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 1 Apr 2020 17:27:16 -0700 Subject: [PATCH 28/41] feat: HTTP API support in SpringBoot 2 implementation. bug: Fixed an issue with the implementation of AsyncContext where it wasn't dispatching if the handler wasn't set --- .../internal/servlet/AwsAsyncContext.java | 8 ++-- .../AwsLambdaServletContainerHandler.java | 4 ++ .../servlet/AwsProxyHttpServletRequest.java | 16 +------ .../internal/servlet/AwsAsyncContextTest.java | 7 ++- .../SpringBootLambdaContainerHandler.java | 34 +++++++++++--- .../spring/SpringBootProxyHandlerBuilder.java | 28 ++++++----- .../proxy/spring/ServletAppTest.java | 31 ++++++++---- .../proxy/spring/WebFluxAppTest.java | 25 ++++++++-- .../spring/servletapp/LambdaHandler.java | 47 +++++++++++++++---- .../proxy/spring/slowapp/LambdaHandler.java | 2 +- .../spring/webfluxapp/LambdaHandler.java | 44 ++++++++++++++--- .../spring/webfluxapp/MessageController.java | 4 +- .../webfluxapp/WebFluxTestApplication.java | 8 +++- 13 files changed, 188 insertions(+), 70 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 0b35d7ba5..57ff308cb 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 @@ -59,10 +59,12 @@ public boolean hasOriginalRequestAndResponse() { 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); + if (dispatched.get()) { + throw new IllegalStateException("Dispatching already started"); + } dispatched.set(true); + notifyListeners(NotificationType.START_ASYNC, null); + handler.doFilter(req, res, ((AwsServletContext)req.getServletContext()).getServletForPath(req.getRequestURI())); } catch (ServletException | IOException e) { notifyListeners(NotificationType.ERROR, e); } 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 6e5249ccd..52631da54 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 @@ -148,6 +148,10 @@ protected FilterChain getFilterChain(HttpServletRequest req, Servlet servlet) { * @throws ServletException */ protected void doFilter(HttpServletRequest request, HttpServletResponse response, Servlet servlet) throws IOException, ServletException { + if (AwsHttpServletRequest.class.isAssignableFrom(request.getClass())) { + ((AwsHttpServletRequest)request).setContainerHandler(this); + } + FilterChain chain = getFilterChain(request, servlet); chain.doFilter(request, response); 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 13f8ef7ef..61b5fddf5 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 @@ -15,19 +15,12 @@ import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.internal.SecurityUtils; -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; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileUploadException; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.NullInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,32 +28,25 @@ import javax.servlet.*; import javax.servlet.http.*; import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.SecurityContext; import java.io.BufferedReader; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.security.Principal; import java.time.Instant; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.TreeMap; /** @@ -566,8 +552,8 @@ public AsyncContext startAsync() @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { + servletRequest.setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ASYNC); 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/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 46a79160a..f379ff32a 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 @@ -1,8 +1,10 @@ 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.LambdaContainerHandler; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; @@ -25,6 +27,7 @@ public class AwsAsyncContextTest { private MockLambdaContext lambdaCtx = new MockLambdaContext(); private MockContainerHandler handler = new MockContainerHandler(); + private AwsProxyHttpServletRequestReader reader = new AwsProxyHttpServletRequestReader(); private AwsServletContextTest.TestServlet srv1 = new AwsServletContextTest.TestServlet("srv1"); private AwsServletContextTest.TestServlet srv2 = new AwsServletContextTest.TestServlet("srv2"); private AwsServletContext ctx = getCtx(); @@ -56,8 +59,8 @@ public void dispatch_sendsToCorrectServlet() { } @Test - public void dispatchNewPath_sendsToCorrectServlet() { - AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest(new AwsProxyRequestBuilder("/srv1/hello", "GET").build(), lambdaCtx, null); + public void dispatchNewPath_sendsToCorrectServlet() throws InvalidRequestEventException { + AwsProxyHttpServletRequest req = (AwsProxyHttpServletRequest) reader.readRequest(new AwsProxyRequestBuilder("/srv1/hello", "GET").build(), null, lambdaCtx, LambdaContainerHandler.getContainerConfig()); req.setResponse(handler.getContainerResponse(req, new CountDownLatch(1))); req.setServletContext(ctx); req.setContainerHandler(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 a3e25fa9b..0eaf58f97 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 @@ -19,6 +19,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.model.HttpApiV2ProxyRequest; import com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveServletEmbeddedServerFactory; import com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory; import com.amazonaws.services.lambda.runtime.Context; @@ -36,6 +37,7 @@ import javax.servlet.Servlet; import javax.servlet.ServletRegistration; +import javax.servlet.http.HttpServletRequest; import java.util.concurrent.CountDownLatch; /** @@ -48,7 +50,7 @@ * @param The incoming event type * @param The expected return type */ -public class SpringBootLambdaContainerHandler extends AwsLambdaServletContainerHandler { +public class SpringBootLambdaContainerHandler extends AwsLambdaServletContainerHandler { private static final String DISPATCHER_SERVLET_REGISTRATION_NAME = "dispatcherServlet"; private final Class springBootInitializer; @@ -83,7 +85,7 @@ public static SpringBootLambdaContainerHandler getInstance() { */ public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class springBootInitializer, String... profiles) throws ContainerInitializationException { - return new SpringBootProxyHandlerBuilder() + return new SpringBootProxyHandlerBuilder() .defaultProxy() .initializationWrapper(new InitializationWrapper()) .springBootApplication(springBootInitializer) @@ -91,6 +93,23 @@ public static SpringBootLambdaContainerHandler getHttpApiV2ProxyHandler(Class springBootInitializer, String... profiles) + throws ContainerInitializationException { + return new SpringBootProxyHandlerBuilder() + .defaultHttpApiV2Proxy() + .initializationWrapper(new InitializationWrapper()) + .springBootApplication(springBootInitializer) + .profiles(profiles) + .buildAndInitialize(); + } + /** * Creates a new container handler with the given reader and writer objects * @@ -106,7 +125,7 @@ public static SpringBootLambdaContainerHandler requestTypeClass, Class responseTypeClass, - RequestReader requestReader, + RequestReader requestReader, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, @@ -138,12 +157,12 @@ public void activateSpringProfiles(String... profiles) { } @Override - protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { return new AwsHttpServletResponse(request, latch); } @Override - protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + protected void handleRequest(HttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { // this method of the AwsLambdaServletContainerHandler sets the servlet context Timer.start("SPRINGBOOT2_HANDLE_REQUEST"); @@ -154,7 +173,10 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt // process filters & invoke servlet Servlet reqServlet = ((AwsServletContext)getServletContext()).getServletForPath(containerRequest.getPathInfo()); - containerRequest.setResponse(containerResponse); + if (AwsHttpServletRequest.class.isAssignableFrom(containerRequest.getClass())) { + ((AwsHttpServletRequest)containerRequest).setServletContext(getServletContext()); + ((AwsHttpServletRequest)containerRequest).setResponse(containerResponse); + } doFilter(containerRequest, containerResponse, reqServlet); Timer.stop("SPRINGBOOT2_HANDLE_REQUEST"); } 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 index bd757ea03..28f36bae2 100644 --- 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 @@ -7,44 +7,46 @@ import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import org.springframework.boot.WebApplicationType; -public final class SpringBootProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< - AwsProxyRequest, +import javax.servlet.http.HttpServletRequest; + +public final class SpringBootProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< + RequestType, AwsProxyResponse, - AwsProxyHttpServletRequest, - SpringBootLambdaContainerHandler, - SpringBootProxyHandlerBuilder> { + HttpServletRequest, + SpringBootLambdaContainerHandler, + SpringBootProxyHandlerBuilder> { private Class springBootInitializer; private String[] profiles; private WebApplicationType applicationType = WebApplicationType.REACTIVE; @Override - protected SpringBootProxyHandlerBuilder self() { + protected SpringBootProxyHandlerBuilder self() { return this; } - public SpringBootProxyHandlerBuilder springBootApplication(Class app) { + public SpringBootProxyHandlerBuilder springBootApplication(Class app) { springBootInitializer = app; return self(); } - public SpringBootProxyHandlerBuilder profiles(String... profiles) { + public SpringBootProxyHandlerBuilder profiles(String... profiles) { this.profiles = profiles; return self(); } - public SpringBootProxyHandlerBuilder servletApplication() { + public SpringBootProxyHandlerBuilder servletApplication() { this.applicationType = WebApplicationType.SERVLET; return self(); } @Override - public SpringBootLambdaContainerHandler build() throws ContainerInitializationException { + public SpringBootLambdaContainerHandler build() throws ContainerInitializationException { validate(); if (springBootInitializer == null) { throw new ContainerInitializationException("Missing spring boot application class in builder", null); } - SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler<>( + SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler( requestTypeClass, responseTypeClass, requestReader, @@ -62,8 +64,8 @@ public SpringBootLambdaContainerHandler build } @Override - public SpringBootLambdaContainerHandler buildAndInitialize() throws ContainerInitializationException { - SpringBootLambdaContainerHandler handler = build(); + 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/serverless/proxy/spring/ServletAppTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java index 60cf83129..4e0c9f23a 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 @@ -12,20 +12,38 @@ import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; +import java.util.Arrays; +import java.util.Collection; + import static org.junit.Assert.assertEquals; +@RunWith(Parameterized.class) public class ServletAppTest { - LambdaHandler handler = new LambdaHandler(); + LambdaHandler handler; MockLambdaContext lambdaContext = new MockLambdaContext(); + private String type; + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); + } + + public ServletAppTest(String reqType) { + type = reqType; + handler = new LambdaHandler(type); + } + @Test public void helloRequest_respondsWithSingleMessage() { - AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/hello", "GET"); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); Assert.assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); } @@ -33,11 +51,10 @@ public void helloRequest_respondsWithSingleMessage() { @Test public void validateRequest_invalidData_respondsWith400() { UserData ud = new UserData(); - AwsProxyRequest req = new AwsProxyRequestBuilder("/validate", "POST") + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/validate", "POST") .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .body(ud) - .build(); + .body(ud); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); try { System.out.println(LambdaContainerHandler.getObjectMapper().writeValueAsString(resp)); @@ -54,9 +71,7 @@ public void validateRequest_invalidData_respondsWith400() { req = new AwsProxyRequestBuilder("/validate", "POST") .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .body(ud2) - .build(); - System.out.println(req.getBody()); + .body(ud2); resp = handler.handleRequest(req, lambdaContext); assertEquals("1", resp.getBody()); assertEquals(400, resp.getStatusCode()); 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 7148e3b67..639138188 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 @@ -8,24 +8,43 @@ import com.amazonaws.serverless.proxy.spring.webfluxapp.MessageController; import org.junit.Assert; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; import static org.junit.Assert.assertEquals; +@RunWith(Parameterized.class) public class WebFluxAppTest { - LambdaHandler handler = new LambdaHandler(); + LambdaHandler handler; MockLambdaContext lambdaContext = new MockLambdaContext(); + private String type; + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); + } + + public WebFluxAppTest(String reqType) { + type = reqType; + handler = new LambdaHandler(type); + } + @Test public void helloRequest_respondsWithSingleMessage() { - AwsProxyRequest req = new AwsProxyRequestBuilder("/single", "GET").build(); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/single", "GET"); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + System.out.println(resp.getBody()); Assert.assertEquals(MessageController.MESSAGE, resp.getBody()); } @Test public void helloDoubleRequest_respondsWithDoubleMessage() { - AwsProxyRequest req = new AwsProxyRequestBuilder("/double", "GET").build(); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/double", "GET"); 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/serverless/proxy/spring/servletapp/LambdaHandler.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java index 8b1d8fff8..88441988e 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 @@ -3,31 +3,58 @@ import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.InitializationWrapper; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; 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; -public class LambdaHandler implements RequestHandler { +public class LambdaHandler implements RequestHandler { private static SpringBootLambdaContainerHandler handler; + private static SpringBootLambdaContainerHandler httpApiHandler; + private String type; - static { + public LambdaHandler(String reqType) { + type = reqType; try { - handler = new SpringBootProxyHandlerBuilder() - .defaultProxy() - .initializationWrapper(new InitializationWrapper()) - .servletApplication() - .springBootApplication(ServletApplication.class) - .buildAndInitialize(); + switch (type) { + case "API_GW": + case "ALB": + handler = new SpringBootProxyHandlerBuilder() + .defaultProxy() + .initializationWrapper(new InitializationWrapper()) + .servletApplication() + .springBootApplication(ServletApplication.class) + .buildAndInitialize(); + break; + case "HTTP_API": + httpApiHandler = new SpringBootProxyHandlerBuilder() + .defaultHttpApiV2Proxy() + .initializationWrapper(new InitializationWrapper()) + .servletApplication() + .springBootApplication(ServletApplication.class) + .buildAndInitialize(); + break; + } } catch (ContainerInitializationException e) { e.printStackTrace(); } } @Override - public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { - return handler.proxy(awsProxyRequest, context); + public AwsProxyResponse handleRequest(AwsProxyRequestBuilder awsProxyRequest, Context context) { + switch (type) { + case "API_GW": + return handler.proxy(awsProxyRequest.build(), context); + case "ALB": + return handler.proxy(awsProxyRequest.alb().build(), context); + case "HTTP_API": + return httpApiHandler.proxy(awsProxyRequest.toHttpApiV2Request(), context); + default: + throw new RuntimeException("Unknown request type: " + type); + } } } 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 index cd4d3fa3a..ec6993a7d 100644 --- 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 @@ -19,7 +19,7 @@ public LambdaHandler() { try { long startTime = Instant.now().toEpochMilli(); System.out.println("startCall: " + startTime); - handler = new SpringBootProxyHandlerBuilder() + handler = new SpringBootProxyHandlerBuilder() .defaultProxy() .asyncInit() .springBootApplication(SlowTestApplication.class) 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 913da5bbc..0eb52a7bc 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,26 +1,58 @@ 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.InitializationWrapper; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; 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; -public class LambdaHandler implements RequestHandler { +public class LambdaHandler implements RequestHandler { private static SpringBootLambdaContainerHandler handler; + private static SpringBootLambdaContainerHandler httpApiHandler; - static { + private String type; + + public LambdaHandler(String reqType) { + type = reqType; try { - handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(WebFluxTestApplication.class); + switch (type) { + case "API_GW": + case "ALB": + handler = new SpringBootProxyHandlerBuilder() + .defaultProxy() + .initializationWrapper(new InitializationWrapper()) + .springBootApplication(WebFluxTestApplication.class) + .buildAndInitialize(); + break; + case "HTTP_API": + httpApiHandler = new SpringBootProxyHandlerBuilder() + .defaultHttpApiV2Proxy() + .initializationWrapper(new InitializationWrapper()) + .springBootApplication(WebFluxTestApplication.class) + .buildAndInitialize(); + break; + } } catch (ContainerInitializationException e) { e.printStackTrace(); } } @Override - public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { - return handler.proxy(awsProxyRequest, context); + public AwsProxyResponse handleRequest(AwsProxyRequestBuilder awsProxyRequest, Context context) { + switch (type) { + case "API_GW": + return handler.proxy(awsProxyRequest.build(), context); + case "ALB": + return handler.proxy(awsProxyRequest.alb().build(), context); + case "HTTP_API": + return httpApiHandler.proxy(awsProxyRequest.toHttpApiV2Request(), context); + default: + throw new RuntimeException("Unknown request type: " + type); + } } } 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 65ce07862..6f287bb37 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 @@ -9,14 +9,14 @@ public class MessageController { public static final String MESSAGE = "Hello"; - @RequestMapping(path="/single", method= RequestMethod.GET) + @RequestMapping(path="/single", method= RequestMethod.GET, produces = {"text/plain"}) Flux singleMessage(){ return Flux.just( MESSAGE ); } - @RequestMapping(path="/double", method= RequestMethod.GET) + @RequestMapping(path="/double", method= RequestMethod.GET, produces={"text/plain"}) Flux doubleMessage(){ return Flux.just( MESSAGE, 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 04d48ff3d..83eef0766 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,12 +1,18 @@ package com.amazonaws.serverless.proxy.spring.webfluxapp; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.context.annotation.Bean; 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 + org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class, + WebMvcAutoConfiguration.class }) public class WebFluxTestApplication { + } From dd6fcdafd6a443f4d71e3852191b3df80dce7849 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 1 Apr 2020 17:42:07 -0700 Subject: [PATCH 29/41] feat: First pass of HTTP API support in struts 2 implementation (#329) --- .../Struts2LambdaContainerHandler.java | 38 +++-- .../proxy/struts2/Struts2AwsProxyTest.java | 155 ++++++++++-------- 2 files changed, 109 insertions(+), 84 deletions(-) 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 25a2f7f96..ab08f5dd8 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 @@ -1,20 +1,12 @@ package com.amazonaws.serverless.proxy.struts2; 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.internal.servlet.AwsHttpServletResponse; -import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +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.model.HttpApiV2ProxyRequest; import com.amazonaws.services.lambda.runtime.Context; import org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter; import org.slf4j.Logger; @@ -23,6 +15,7 @@ import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; import javax.servlet.Servlet; +import javax.servlet.http.HttpServletRequest; import java.util.EnumSet; import java.util.concurrent.CountDownLatch; @@ -32,7 +25,7 @@ * @param request type * @param response type */ -public class Struts2LambdaContainerHandler extends AwsLambdaServletContainerHandler { +public class Struts2LambdaContainerHandler extends AwsLambdaServletContainerHandler { private static final Logger log = LoggerFactory.getLogger(Struts2LambdaContainerHandler.class); @@ -55,9 +48,19 @@ public static Struts2LambdaContainerHandler g new AwsProxyExceptionHandler()); } + public static Struts2LambdaContainerHandler getHttpApiV2ProxyHandler() { + return new Struts2LambdaContainerHandler( + HttpApiV2ProxyRequest.class, + AwsProxyResponse.class, + new AwsHttpApiV2HttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsHttpApiV2SecurityContextWriter(), + new AwsProxyExceptionHandler()); + } + public Struts2LambdaContainerHandler(Class requestTypeClass, Class responseTypeClass, - RequestReader requestReader, + RequestReader requestReader, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler) { @@ -68,12 +71,13 @@ public Struts2LambdaContainerHandler(Class requestTypeClass, Timer.stop(TIMER_STRUTS_2_CONTAINER_CONSTRUCTOR); } - protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + @Override + protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { return new AwsHttpServletResponse(request, latch); } @Override - protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, + protected void handleRequest(HttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext) throws Exception { Timer.start(TIMER_STRUTS_2_HANDLE_REQUEST); @@ -81,7 +85,9 @@ protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, initialize(); } - httpServletRequest.setServletContext(this.getServletContext()); + if (AwsHttpServletRequest.class.isAssignableFrom(httpServletRequest.getClass())) { + ((AwsHttpServletRequest)httpServletRequest).setServletContext(this.getServletContext()); + } this.doFilter(httpServletRequest, httpServletResponse, null); String responseStatusCode = httpServletResponse.getHeader(HEADER_STRUTS_STATUS_CODE); if (responseStatusCode != null) { 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 45e14ccdb..447b907bc 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 @@ -17,6 +17,7 @@ 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.HttpApiV2ProxyRequest; import com.amazonaws.serverless.proxy.struts2.echoapp.EchoAction; import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonProcessingException; @@ -25,23 +26,25 @@ import org.apache.commons.codec.binary.Base64; import org.apache.struts2.StrutsJUnit4TestCase; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import javax.ws.rs.core.Response; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; /** * Unit test class for the Struts2 AWS_PROXY default implementation */ +@RunWith(Parameterized.class) public class Struts2AwsProxyTest extends StrutsJUnit4TestCase { private static final String CUSTOM_HEADER_KEY = "x-custom-header"; private static final String CUSTOM_HEADER_VALUE = "my-custom-value"; @@ -55,17 +58,42 @@ public class Struts2AwsProxyTest extends StrutsJUnit4TestCase { private static ObjectMapper objectMapper = new ObjectMapper(); private final Struts2LambdaContainerHandler handler = Struts2LambdaContainerHandler .getAwsProxyHandler(); + private final Struts2LambdaContainerHandler httpApiHandler = Struts2LambdaContainerHandler + .getHttpApiV2ProxyHandler(); private static Context lambdaContext = new MockLambdaContext(); + private String type; + + public Struts2AwsProxyTest(String reqType) { + type = reqType; + } + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); + } + + private AwsProxyResponse executeRequest(AwsProxyRequestBuilder requestBuilder, Context lambdaContext) { + switch (type) { + case "API_GW": + return handler.proxy(requestBuilder.build(), lambdaContext); + case "ALB": + return handler.proxy(requestBuilder.alb().build(), lambdaContext); + case "HTTP_API": + return httpApiHandler.proxy(requestBuilder.toHttpApiV2Request(), lambdaContext); + default: + throw new RuntimeException("Unknown request type: " + type); + } + } + @Test public void headers_getHeaders_echo() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "GET") .queryString("mode", "headers") .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .json() - .build(); + .json(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); @@ -74,24 +102,23 @@ public void headers_getHeaders_echo() { @Test public void context_servletResponse_setCustomHeader() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", "GET") .queryString("customHeader", "true") - .json() - .build(); + .json(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertTrue(output.getMultiValueHeaders().containsKey("XX")); } @Test public void context_serverInfo_correctContext() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET") + assumeTrue("API_GW".equals(type)); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", "GET") .queryString(QUERY_STRING_KEY, "Hello Struts2") .header("Content-Type", "application/json") - .queryString("contentType", "true") - .build(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + .queryString("contentType", "true"); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); @@ -100,14 +127,13 @@ public void context_serverInfo_correctContext() { @Test public void queryString_uriInfo_echo() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "GET") .queryString("mode", "query-string") .queryString(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .json() - .build(); + .json(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); @@ -116,13 +142,12 @@ public void queryString_uriInfo_echo() { @Test public void requestScheme_valid_expectHttps() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "GET") .queryString("mode", "scheme") .queryString(QUERY_STRING_KEY, QUERY_STRING_ENCODED_VALUE) - .json() - .build(); + .json(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); @@ -131,13 +156,13 @@ public void requestScheme_valid_expectHttps() { @Test public void authorizer_securityContext_customPrincipalSuccess() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + assumeTrue("API_GW".equals(type)); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "GET") .queryString("mode", "principal") .json() - .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID) - .build(); + .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); @@ -146,32 +171,30 @@ public void authorizer_securityContext_customPrincipalSuccess() { @Test public void errors_unknownRoute_expect404() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/unknown", "GET").build(); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/unknown", "GET"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(404, output.getStatusCode()); } @Test public void error_contentType_invalidContentType() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "POST") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "POST") .queryString("mode", "content-type") .header("Content-Type", "application/octet-stream") - .body("asdasdasd") - .build(); + .body("asdasdasd"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(415, output.getStatusCode()); } @Test public void error_statusCode_methodNotAllowed() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "POST") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "POST") .queryString("mode", "not-allowed") - .json() - .build(); + .json(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(405, output.getStatusCode()); } @@ -180,12 +203,11 @@ public void error_statusCode_methodNotAllowed() { public void responseBody_responseWriter_validBody() throws JsonProcessingException { Map value = new HashMap<>(); value.put(QUERY_STRING_KEY, CUSTOM_HEADER_VALUE); - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "POST") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", "POST") .json() - .body(objectMapper.writeValueAsString(value)) - .build(); + .body(objectMapper.writeValueAsString(value)); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertNotNull(output.getBody()); @@ -194,32 +216,30 @@ public void responseBody_responseWriter_validBody() throws JsonProcessingExcepti @Test public void statusCode_responseStatusCode_customStatusCode() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "GET") .queryString("mode", "custom-status-code") .queryString("status", "201") - .json() - .build(); + .json(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(201, output.getStatusCode()); } @Test public void base64_binaryResponse_base64Encoding() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET").build(); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", "GET"); - AwsProxyResponse response = handler.proxy(request, lambdaContext); + AwsProxyResponse response = executeRequest(request, lambdaContext); assertNotNull(response.getBody()); assertTrue(Base64.isBase64(response.getBody())); } @Test public void exception_mapException_mapToNotImplemented() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "POST") - .queryString("mode", "not-implemented") - .build(); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "POST") + .queryString("mode", "not-implemented"); - AwsProxyResponse response = handler.proxy(request, lambdaContext); + AwsProxyResponse response = executeRequest(request, lambdaContext); assertNotNull(response.getBody()); assertEquals("null", response.getBody()); assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), response.getStatusCode()); @@ -227,12 +247,11 @@ public void exception_mapException_mapToNotImplemented() { @Test public void stripBasePath_route_shouldRouteCorrectly() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/custompath/echo", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/custompath/echo", "GET") .json() - .queryString(QUERY_STRING_KEY, "stripped") - .build(); + .queryString(QUERY_STRING_KEY, "stripped"); handler.stripBasePath("/custompath"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); validateSingleValueModel(output, "stripped"); handler.stripBasePath(""); @@ -240,29 +259,30 @@ public void stripBasePath_route_shouldRouteCorrectly() { @Test public void stripBasePath_route_shouldReturn404() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/custompath/echo/status-code", "GET") + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/custompath/echo/status-code", "GET") .json() - .queryString("status", "201") - .build(); + .queryString("status", "201"); handler.stripBasePath("/custom"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); + AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(404, output.getStatusCode()); handler.stripBasePath(""); } @Test public void securityContext_injectPrincipal_expectPrincipalName() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + assumeTrue("API_GW".equals(type)); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "GET") .queryString("mode", "principal") - .authorizerPrincipal(USER_PRINCIPAL).build(); + .authorizerPrincipal(USER_PRINCIPAL); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, USER_PRINCIPAL); } @Test public void queryParam_encoding_expectUnencodedParam() { + assumeTrue("API_GW".equals(type)); String paramValue = "p%2Fz%2B3"; String decodedParam = ""; try { @@ -271,21 +291,20 @@ public void queryParam_encoding_expectUnencodedParam() { e.printStackTrace(); fail("Could not decode parameter"); } - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET").queryString(QUERY_STRING_KEY, decodedParam) - .build(); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", "GET").queryString(QUERY_STRING_KEY, decodedParam); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, decodedParam); } @Test public void queryParam_encoding_expectEncodedParam() { + assumeTrue("API_GW".equals(type)); String paramValue = "p%2Fz%2B3"; - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET").queryString(QUERY_STRING_KEY, paramValue) - .build(); + AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", "GET").queryString(QUERY_STRING_KEY, paramValue); - AwsProxyResponse resp = handler.proxy(request, lambdaContext); + AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, paramValue); } From cdf7a9094a6b074c6a2b6c3389f597c8e301bb40 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Thu, 2 Apr 2020 08:28:31 -0700 Subject: [PATCH 30/41] fix: Added support for HTTP APIs to the request dispatcher --- .../AwsHttpApiV2ProxyHttpServletRequest.java | 4 ++++ .../servlet/AwsProxyRequestDispatcher.java | 20 +++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java index c75bc07d8..c4f51f66a 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java @@ -51,6 +51,10 @@ public AwsHttpApiV2ProxyHttpServletRequest(HttpApiV2ProxyRequest req, Context la headers = headersMapToMultiValue(request.getHeaders()); } + public HttpApiV2ProxyRequest getRequest() { + return request; + } + @Override public String getAuthType() { // TODO 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 2ffcc97de..806ba9a49 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 @@ -3,6 +3,7 @@ import com.amazonaws.serverless.proxy.internal.SecurityUtils; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,6 +15,7 @@ import java.io.IOException; import static com.amazonaws.serverless.proxy.RequestReader.API_GATEWAY_EVENT_PROPERTY; +import static com.amazonaws.serverless.proxy.RequestReader.HTTP_API_EVENT_PROPERTY; import static com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest.DISPATCHER_TYPE_ATTRIBUTE; @@ -115,12 +117,22 @@ void setRequestPath(ServletRequest req, final String destinationPath) { ((AwsProxyHttpServletRequest) req).getAwsProxyRequest().setPath(dispatchTo); return; } + if (req instanceof AwsHttpApiV2ProxyHttpServletRequest) { + ((AwsHttpApiV2ProxyHttpServletRequest) req).getRequest().setRawPath(destinationPath); + return; + } - 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"); + log.debug("Request is not an proxy request generated by this library, attempting to extract the proxy event type from the request attributes"); + if (req.getAttribute(API_GATEWAY_EVENT_PROPERTY) != null && req.getAttribute(API_GATEWAY_EVENT_PROPERTY) instanceof AwsProxyRequest) { + ((AwsProxyRequest)req.getAttribute(API_GATEWAY_EVENT_PROPERTY)).setPath(dispatchTo); + return; } - ((AwsProxyRequest)req.getAttribute(API_GATEWAY_EVENT_PROPERTY)).setPath(dispatchTo); + if (req.getAttribute(HTTP_API_EVENT_PROPERTY) != null && req.getAttribute(HTTP_API_EVENT_PROPERTY) instanceof HttpApiV2ProxyRequest) { + ((HttpApiV2ProxyRequest)req.getAttribute(HTTP_API_EVENT_PROPERTY)).setRawPath(destinationPath); + return; + } + + throw new IllegalStateException("Could not set new target path for the given ServletRequest object"); } private Servlet getServlet(HttpServletRequest req) { From f9eb729ca198faeec554ba9a549253b7da25bd2d Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Thu, 2 Apr 2020 08:56:30 -0700 Subject: [PATCH 31/41] chore(deps): Dependency bump all around. Rotated Jersey ci versions --- .github/workflows/continuous-integration-workflow.yml | 4 ++-- aws-serverless-java-container-jersey/pom.xml | 2 +- .../src/main/resources/archetype-resources/pom.xml | 4 ++-- .../main/resources/archetype-resources/build.gradle | 2 +- .../src/main/resources/archetype-resources/pom.xml | 2 +- .../main/resources/archetype-resources/build.gradle | 6 +++--- .../src/main/resources/archetype-resources/pom.xml | 2 +- .../main/resources/archetype-resources/build.gradle | 2 +- .../src/main/resources/archetype-resources/pom.xml | 2 +- .../main/resources/archetype-resources/build.gradle | 10 +++++----- .../src/main/resources/archetype-resources/pom.xml | 4 ++-- owasp-suppression.xml | 10 ---------- pom.xml | 9 +++++---- samples/jersey/pet-store/build.gradle | 6 +++--- samples/jersey/pet-store/pom.xml | 4 ++-- samples/spark/pet-store/build.gradle | 2 +- samples/spark/pet-store/pom.xml | 2 +- samples/spring/pet-store/build.gradle | 6 +++--- samples/spring/pet-store/pom.xml | 2 +- samples/springboot2/pet-store/build.gradle | 6 +++--- samples/springboot2/pet-store/pom.xml | 4 ++-- samples/struts2/pet-store/build.gradle | 10 +++++----- samples/struts2/pet-store/pom.xml | 4 ++-- 23 files changed, 48 insertions(+), 57 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 8166497e4..025848302 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -22,12 +22,12 @@ jobs: - uses: actions/checkout@v2 - name: Build latest run: ./gha_build.sh jersey true true - - name: Build Jersey 2.26 - run: ./gha_build.sh jersey false false -Djersey.version=2.26 - name: Build Jersey 2.27 run: ./gha_build.sh jersey false false -Djersey.version=2.27 - name: Build Jersey 2.28 run: ./gha_build.sh jersey false false -Djersey.version=2.28 + - name: Build Jersey 2.29 + run: ./gha_build.sh jersey false false -Djersey.version=2.29.1 build_spark: name: Build and test Spark diff --git a/aws-serverless-java-container-jersey/pom.xml b/aws-serverless-java-container-jersey/pom.xml index 28edd3c9f..b1c9b0318 100644 --- a/aws-serverless-java-container-jersey/pom.xml +++ b/aws-serverless-java-container-jersey/pom.xml @@ -16,7 +16,7 @@ - 2.29.1 + 2.30.1 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 3282eb25d..9fe135430 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,8 +14,8 @@ 1.8 1.8 - 2.29.1 - 2.9.10 + 2.30.1 + 2.10.3 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 f4e280ce7..e915d4511 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 @@ -9,7 +9,7 @@ dependencies { compile ( '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.10', + 'com.fasterxml.jackson.core:jackson-databind:2.10.3', '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 055fc4321..579d10607 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.10 + 2.10.3 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 6447fe4e8..e73185939 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.13.RELEASE', - 'org.springframework:spring-context:5.1.13.RELEASE', + 'org.springframework:spring-webmvc:5.2.5.RELEASE', + 'org.springframework:spring-context:5.2.5.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.10', + 'com.fasterxml.jackson.core:jackson-databind:2.10.3', 'com.amazonaws:aws-lambda-java-log4j2:1.1.0', ) 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 2f6d386d7..a41ddd6ec 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.13.RELEASE + 5.2.5.RELEASE 4.12 2.8.2 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 9d9d17af1..2c95f91d0 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.12.RELEASE', + 'org.springframework.boot:spring-boot-starter-web:2.2.6.RELEASE', 'com.amazonaws.serverless:aws-serverless-java-container-springboot2:[1.0,)', 'io.symphonia:lambda-logging:1.0.1' ) 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 8d1fc100c..e25916482 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.12.RELEASE + 2.2.6.RELEASE 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 ee08f5d4c..9e4b4ab18 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.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', + 'org.apache.struts:struts2-convention-plugin:2.5.22', + 'org.apache.struts:struts2-rest-plugin:2.5.22', + 'org.apache.struts:struts2-bean-validation-plugin:2.5.22', + 'org.apache.struts:struts2-junit-plugin:2.5.22', '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.10', + 'com.fasterxml.jackson.core:jackson-databind:2.10.3', '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/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/pom.xml index 3e224f4a5..935c0e9f2 100644 --- a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/pom.xml @@ -15,8 +15,8 @@ 1.8 1.8 - 2.5.20 - 2.9.9 + 2.5.22 + 2.10.3 4.12 2.11.1 diff --git a/owasp-suppression.xml b/owasp-suppression.xml index fe70f749b..0997df5c3 100644 --- a/owasp-suppression.xml +++ b/owasp-suppression.xml @@ -27,14 +27,4 @@ cpe:/a:restful_web_services_project:restful_web_services:7.x-2.1::~~~drupal~~ - - - - - - cpe:/a:slf4j:slf4j:1.8.0 - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 298bdc8b2..e61b9b739 100644 --- a/pom.xml +++ b/pom.xml @@ -45,8 +45,9 @@ 0.7 - 5.2.2 - 2.10.1 + 5.3.2 + 2.10.3 + 1.8.0-beta4 @@ -62,14 +63,14 @@ org.slf4j slf4j-api - 1.8.0-beta2 + ${slf4j.version} org.slf4j slf4j-simple - 1.8.0-beta2 + ${slf4j.version} test diff --git a/samples/jersey/pet-store/build.gradle b/samples/jersey/pet-store/build.gradle index 064fdf373..88e34767e 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.10', + 'com.fasterxml.jackson.core:jackson-databind:2.10.3', 'io.symphonia:lambda-logging:1.0.1' ) - compile("org.glassfish.jersey.media:jersey-media-json-jackson:2.10.1") { + compile("org.glassfish.jersey.media:jersey-media-json-jackson:2.30.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.29.1") { + compile("org.glassfish.jersey.inject:jersey-hk2:2.30.1") { exclude group: 'javax.inject', module: "javax.inject" } } diff --git a/samples/jersey/pet-store/pom.xml b/samples/jersey/pet-store/pom.xml index 6761c6210..bacdbe042 100644 --- a/samples/jersey/pet-store/pom.xml +++ b/samples/jersey/pet-store/pom.xml @@ -26,8 +26,8 @@ 1.8 1.8 - 2.29.1 - 2.10.1 + 2.30.1 + 2.10.3 diff --git a/samples/spark/pet-store/build.gradle b/samples/spark/pet-store/build.gradle index 00ac4797c..01605a978 100644 --- a/samples/spark/pet-store/build.gradle +++ b/samples/spark/pet-store/build.gradle @@ -9,7 +9,7 @@ dependencies { compile ( 'com.sparkjava:spark-core:2.9.1', 'com.amazonaws.serverless:aws-serverless-java-container-spark:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.10.1', + 'com.fasterxml.jackson.core:jackson-databind:2.10.3', 'io.symphonia:lambda-logging:1.0.1' ) } diff --git a/samples/spark/pet-store/pom.xml b/samples/spark/pet-store/pom.xml index 4bdd90956..26a2f256b 100644 --- a/samples/spark/pet-store/pom.xml +++ b/samples/spark/pet-store/pom.xml @@ -26,7 +26,7 @@ 1.8 1.8 - 2.10.1 + 2.10.3 2.9.1 diff --git a/samples/spring/pet-store/build.gradle b/samples/spring/pet-store/build.gradle index a8e815fbb..433f5741b 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.13.RELEASE', - 'org.springframework:spring-context:5.1.13.RELEASE', + 'org.springframework:spring-webmvc:5.2.5.RELEASE', + 'org.springframework:spring-context:5.2.5.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.10', + 'com.fasterxml.jackson.core:jackson-databind:2.10.3', 'com.amazonaws:aws-lambda-java-log4j2:1.1.0', ) } diff --git a/samples/spring/pet-store/pom.xml b/samples/spring/pet-store/pom.xml index 86d26471d..2798eafa1 100644 --- a/samples/spring/pet-store/pom.xml +++ b/samples/spring/pet-store/pom.xml @@ -26,7 +26,7 @@ 1.8 1.8 - 5.2.3.RELEASE + 5.2.5.RELEASE 4.12 2.8.2 diff --git a/samples/springboot2/pet-store/build.gradle b/samples/springboot2/pet-store/build.gradle index fd4e81817..10480c9a4 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.12.RELEASE' + id 'org.springframework.boot' version '2.2.6.RELEASE' } apply plugin: 'java' @@ -11,10 +11,10 @@ repositories { dependencies { compile ( - implementation('org.springframework.boot:spring-boot-starter-web:2.1.12.RELEASE') { + implementation('org.springframework.boot:spring-boot-starter-web:2.2.6.RELEASE') { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' }, - 'com.amazonaws.serverless:aws-serverless-java-container-springboot2:[1.0,)', + 'com.amazonaws.serverless:aws-serverless-java-container-springboot2:[1.4,)', '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 15bf3e671..f9444cbf4 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.12.RELEASE + 2.2.6.RELEASE @@ -33,7 +33,7 @@ com.amazonaws.serverless aws-serverless-java-container-springboot2 - [0.1,) + [1.4,) diff --git a/samples/struts2/pet-store/build.gradle b/samples/struts2/pet-store/build.gradle index 2d7e42500..24f018973 100644 --- a/samples/struts2/pet-store/build.gradle +++ b/samples/struts2/pet-store/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.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', + 'org.apache.struts:struts2-convention-plugin:2.5.22', + 'org.apache.struts:struts2-rest-plugin:2.5.22', + 'org.apache.struts:struts2-bean-validation-plugin:2.5.22', + 'org.apache.struts:struts2-junit-plugin:2.5.22', '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.10.1', + 'com.fasterxml.jackson.core:jackson-databind:2.10.3', '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/struts2/pet-store/pom.xml b/samples/struts2/pet-store/pom.xml index 9689fc6a7..b761207d6 100644 --- a/samples/struts2/pet-store/pom.xml +++ b/samples/struts2/pet-store/pom.xml @@ -26,8 +26,8 @@ 1.8 1.8 - 2.5.20 - 2.10.1 + 2.5.22 + 2.10.3 4.12 2.11.1 From 0b769241b971209d84baba1faa72c5d63141ecc5 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Thu, 2 Apr 2020 13:06:48 -0700 Subject: [PATCH 32/41] fix: Updated stream handling logic to work with reactive applications as suggested in #316 --- .../AwsHttpApiV2ProxyHttpServletRequest.java | 15 ++-- .../servlet/AwsHttpServletRequest.java | 5 +- .../servlet/AwsProxyHttpServletRequest.java | 68 +++---------------- .../servlet/AwsServletInputStream.java | 64 +++++++++++++++++ .../AwsProxyHttpServletRequestTest.java | 2 +- .../proxy/spring/ServletAppTest.java | 15 +++- .../proxy/spring/WebFluxAppTest.java | 15 ++++ .../spring/servletapp/MessageController.java | 12 ++-- .../proxy/spring/servletapp/MessageData.java | 20 ++++++ .../spring/webfluxapp/MessageController.java | 10 +++ .../proxy/spring/webfluxapp/MessageData.java | 20 ++++++ .../webfluxapp/WebFluxTestApplication.java | 6 +- 12 files changed, 178 insertions(+), 74 deletions(-) create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageData.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageData.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java index c4f51f66a..8fdb55ba7 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java @@ -217,7 +217,7 @@ public T upgrade(Class aClass) throws IOExcept @Override public String getCharacterEncoding() { if (headers == null) { - return null; + return config.getDefaultContentCharset(); } return parseCharacterEncoding(headers.getFirst(HttpHeaders.CONTENT_TYPE)); } @@ -258,11 +258,6 @@ public String getContentType() { return headers.getFirst(HttpHeaders.CONTENT_TYPE); } - @Override - public ServletInputStream getInputStream() throws IOException { - return bodyStringToInputStream(request.getBody(), request.isBase64Encoded()); - } - @Override public String getParameter(String s) { String queryStringParameter = getFirstQueryParamValue(queryString, s, config.isQueryStringCaseSensitive()); @@ -347,6 +342,14 @@ public int getServerPort() { return 443; // default port } + @Override + public ServletInputStream getInputStream() throws IOException { + if (requestInputStream == null) { + requestInputStream = new AwsServletInputStream(bodyStringToInputStream(request.getBody(), request.isBase64Encoded())); + } + return requestInputStream; + } + @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new StringReader(request.getBody())); 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 f88bdcaf9..f02cefd1c 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 @@ -89,6 +89,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { protected AwsHttpServletResponse response; protected AwsLambdaServletContainerHandler containerHandler; + protected ServletInputStream requestInputStream; private static Logger log = LoggerFactory.getLogger(AwsHttpServletRequest.class); @@ -425,7 +426,7 @@ protected String appendCharacterEncoding(String currentContentType, String newEn protected ServletInputStream bodyStringToInputStream(String body, boolean isBase64Encoded) throws IOException { if (body == null) { - return new AwsProxyHttpServletRequest.AwsServletInputStream(new NullInputStream(0, false, false)); + return new AwsServletInputStream(new NullInputStream(0, false, false)); } byte[] bodyBytes; if (isBase64Encoded) { @@ -443,7 +444,7 @@ protected ServletInputStream bodyStringToInputStream(String body, boolean isBase } } ByteArrayInputStream requestBodyStream = new ByteArrayInputStream(bodyBytes); - return new AwsProxyHttpServletRequest.AwsServletInputStream(requestBodyStream); + return new AwsServletInputStream(requestBodyStream); } protected String getFirstQueryParamValue(MultiValuedTreeMap queryString, String key, boolean isCaseSensitive) { 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 61b5fddf5..97ae055bd 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 @@ -21,7 +21,6 @@ import com.amazonaws.services.lambda.runtime.Context; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import org.apache.commons.io.input.NullInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,7 +31,6 @@ import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.security.Principal; @@ -291,6 +289,9 @@ public T upgrade(Class aClass) @Override public String getCharacterEncoding() { + if (request.getMultiValueHeaders() == null) { + return config.getDefaultContentCharset(); + } return parseCharacterEncoding(request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); } @@ -341,14 +342,6 @@ public String getContentType() { return contentTypeHeader; } - - @Override - public ServletInputStream getInputStream() - throws IOException { - return bodyStringToInputStream(request.getBody(), request.isBase64Encoded()); - } - - @Override public String getParameter(String s) { String queryStringParameter = getFirstQueryParamValue(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive()); @@ -441,6 +434,14 @@ public int getServerPort() { } } + @Override + public ServletInputStream getInputStream() throws IOException { + if (requestInputStream == null) { + requestInputStream = new AwsServletInputStream(bodyStringToInputStream(request.getBody(), request.isBase64Encoded())); + } + return requestInputStream; + } + @Override public BufferedReader getReader() @@ -594,51 +595,4 @@ private List getHeaderValues(String key) { } - public static class AwsServletInputStream extends ServletInputStream { - - private InputStream bodyStream; - private ReadListener listener; - - public AwsServletInputStream(InputStream body) { - bodyStream = body; - } - - - @Override - public boolean isFinished() { - return true; - } - - - @Override - public boolean isReady() { - return true; - } - - - @Override - public void setReadListener(ReadListener readListener) { - listener = readListener; - try { - listener.onDataAvailable(); - } catch (IOException e) { - log.error("Data not available on input stream", e); - } - } - - - @Override - public int read() - throws IOException { - if (bodyStream == null || bodyStream instanceof NullInputStream) { - return -1; - } - int readByte = bodyStream.read(); - if (bodyStream.available() == 0 && listener != null) { - listener.onAllDataRead(); - } - return readByte; - } - - } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java new file mode 100644 index 000000000..a80d2e807 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java @@ -0,0 +1,64 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import org.apache.commons.io.input.NullInputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class AwsServletInputStream extends ServletInputStream { + private static Logger log = LoggerFactory.getLogger(AwsServletInputStream.class); + private InputStream bodyStream; + private ReadListener listener; + private boolean finished; + + public AwsServletInputStream(InputStream body) { + bodyStream = body; + finished = false; + } + + @Override + public boolean isFinished() { + return finished; + } + + @Override + public boolean isReady() { + if (finished && listener != null) { + try { + listener.onAllDataRead(); + } catch (IOException e) { + log.error("Could not notify listeners that input stream data is ready", e); + throw new RuntimeException(e); + } + } + return !finished; + } + + @Override + public void setReadListener(ReadListener readListener) { + listener = readListener; + try { + listener.onDataAvailable(); + } catch (IOException e) { + log.error("Could not notify listeners that data is available", e); + throw new RuntimeException(e); + } + } + + @Override + public int read() + throws IOException { + if (bodyStream == null || bodyStream instanceof NullInputStream) { + return -1; + } + int readByte = bodyStream.read(); + if (readByte == -1) { + finished = true; + } + return readByte; + } +} 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 47440eb9d..732c3f69b 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 @@ -497,7 +497,7 @@ public void inputStream_emptyBody_expectNullInputStream() { try { InputStream is = req.getInputStream(); - assertTrue(is.getClass() == AwsProxyHttpServletRequest.AwsServletInputStream.class); + assertTrue(is.getClass() == AwsServletInputStream.class); assertEquals(0, is.available()); } catch (IOException e) { fail("Could not get input stream"); 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 4e0c9f23a..f5b205dc0 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 @@ -3,11 +3,10 @@ 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.SecurityConfig; import com.amazonaws.serverless.proxy.spring.servletapp.LambdaHandler; import com.amazonaws.serverless.proxy.spring.servletapp.MessageController; +import com.amazonaws.serverless.proxy.spring.servletapp.MessageData; import com.amazonaws.serverless.proxy.spring.servletapp.UserData; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; @@ -22,6 +21,7 @@ import java.util.Collection; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; @RunWith(Parameterized.class) public class ServletAppTest { @@ -76,4 +76,15 @@ public void validateRequest_invalidData_respondsWith400() { assertEquals("1", resp.getBody()); assertEquals(400, resp.getStatusCode()); } + + @Test + public void messageObject_parsesObject_returnsCorrectMessage() throws JsonProcessingException { + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/message", "POST") + .json() + .body(new MessageData("test message")); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + assertEquals("test 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 639138188..811a1ab55 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,14 @@ 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.webfluxapp.LambdaHandler; import com.amazonaws.serverless.proxy.spring.webfluxapp.MessageController; +import com.amazonaws.serverless.proxy.spring.webfluxapp.MessageData; +import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -15,6 +18,7 @@ import java.util.Collection; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; @RunWith(Parameterized.class) public class WebFluxAppTest { @@ -49,4 +53,15 @@ public void helloDoubleRequest_respondsWithDoubleMessage() { assertEquals(MessageController.MESSAGE + MessageController.MESSAGE, resp.getBody()); } + + @Test + public void messageObject_parsesObject_returnsCorrectMessage() throws JsonProcessingException { + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/message", "POST") + .json() + .body(new MessageData("test message")); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + assertEquals("test message", resp.getBody()); + } } 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 3b60291b7..8352317a9 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,15 +1,11 @@ package com.amazonaws.serverless.proxy.spring.servletapp; import org.springframework.http.ResponseEntity; -import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; -import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.RequestBody; 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.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; import javax.validation.Valid; @@ -30,4 +26,12 @@ public ResponseEntity validateBody(@RequestBody @Valid UserData userData } return ResponseEntity.ok(VALID_MESSAGE); } + + @RequestMapping(path="/message", method = RequestMethod.POST, produces={"text/plain"}, consumes = {"application/json"}) + public String returnMessage(@RequestBody MessageData data) { + if (data == null) { + throw new RuntimeException("No message data"); + } + return data.getMessage(); + } } diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageData.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageData.java new file mode 100644 index 000000000..129101cbe --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageData.java @@ -0,0 +1,20 @@ +package com.amazonaws.serverless.proxy.spring.servletapp; + +public class MessageData { + private String message; + + public MessageData() { + } + + public MessageData(String m) { + setMessage(m); + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} 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 6f287bb37..a04604618 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,8 +1,10 @@ package com.amazonaws.serverless.proxy.spring.webfluxapp; +import org.springframework.web.bind.annotation.RequestBody; 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; import reactor.core.publisher.Flux; @RestController @@ -23,4 +25,12 @@ Flux doubleMessage(){ MESSAGE ); } + + @RequestMapping(path="/message", method = RequestMethod.POST, produces={"text/plain"}, consumes = {"application/json"}) + public Flux returnMessage(@RequestBody MessageData data) { + if (data == null) { + throw new RuntimeException("No message data"); + } + return Flux.just(data.getMessage()); + } } \ No newline at end of file diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageData.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageData.java new file mode 100644 index 000000000..2be6b4f2d --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageData.java @@ -0,0 +1,20 @@ +package com.amazonaws.serverless.proxy.spring.webfluxapp; + +public class MessageData { + private String message; + + public MessageData() { + } + + public MessageData(String m) { + setMessage(m); + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} 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 83eef0766..70e0c9934 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 @@ -4,14 +4,16 @@ import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; @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, - WebMvcAutoConfiguration.class + org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class }) public class WebFluxTestApplication { From 778af718151d7d12c82591ea8b9a0056d6224d3c Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 7 Apr 2020 08:11:09 -0700 Subject: [PATCH 33/41] test: Added unit test to replicate #333 --- aws-serverless-java-container-springboot2/pom.xml | 7 +++++++ .../serverless/proxy/spring/ServletAppTest.java | 11 ++++++++++- .../proxy/spring/servletapp/MessageController.java | 10 ++++++---- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/aws-serverless-java-container-springboot2/pom.xml b/aws-serverless-java-container-springboot2/pom.xml index 2fac94aca..10d995fee 100644 --- a/aws-serverless-java-container-springboot2/pom.xml +++ b/aws-serverless-java-container-springboot2/pom.xml @@ -166,6 +166,13 @@ 17.0.0 compile + + + javax.activation + activation + 1.1.1 + test + 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 f5b205dc0..ac610d2a1 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 @@ -78,7 +78,7 @@ public void validateRequest_invalidData_respondsWith400() { } @Test - public void messageObject_parsesObject_returnsCorrectMessage() throws JsonProcessingException { + public void messageObject_parsesObject_returnsCorrectMessage() { AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/message", "POST") .json() .body(new MessageData("test message")); @@ -87,4 +87,13 @@ public void messageObject_parsesObject_returnsCorrectMessage() throws JsonProces assertEquals(200, resp.getStatusCode()); assertEquals("test message", resp.getBody()); } + + @Test + public void echoMessage_fileNameLikeParameter_returnsMessage() { + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/echo/test.test.test", "GET"); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + assertEquals("test.test.test", resp.getBody()); + } } 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 8352317a9..a03846f39 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 @@ -2,10 +2,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.Errors; -import org.springframework.web.bind.annotation.RequestBody; -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.bind.annotation.*; import javax.validation.Valid; @@ -34,4 +31,9 @@ public String returnMessage(@RequestBody MessageData data) { } return data.getMessage(); } + + @RequestMapping(path="/echo/{message}", method=RequestMethod.GET) + public String returnPathMessage(@PathVariable(value="message") String message) { + return message; + } } From bf24b2056e309a8a3ac34bd2a163abb73b6c0195 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 7 Apr 2020 12:40:21 -0700 Subject: [PATCH 34/41] feat: New configuration parameter to skip exception mapping and allow exception to bubble up from #307 --- .../internal/LambdaContainerHandler.java | 10 +- .../proxy/model/ContainerConfig.java | 20 ++++ .../internal/LambdaContainerHandlerTest.java | 105 ++++++++++++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandlerTest.java 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 3bb401d17..a3a657e71 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 @@ -223,7 +223,15 @@ public ResponseType proxy(RequestType request, Context context) { // the latch will do nothing latch.countDown(); - return exceptionHandler.handle(e); + if (getContainerConfig().isDisableExceptionMapper()) { + if (RuntimeException.class.isAssignableFrom(e.getClass())) { + throw (RuntimeException) e; + } else { + throw new RuntimeException(e); + } + } else { + return exceptionHandler.handle(e); + } } } 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 e5c80d635..6d66fd7fb 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 @@ -48,6 +48,7 @@ public static ContainerConfig defaultConfig() { private boolean queryStringCaseSensitive; private final HashSet binaryContentTypes; private int initializationTimeout; + private boolean disableExceptionMapper; public ContainerConfig() { validFilePaths = new ArrayList<>(); @@ -297,4 +298,23 @@ public int getInitializationTimeout() { public void setInitializationTimeout(int initializationTimeout) { this.initializationTimeout = initializationTimeout; } + + /** + * Whether the framework will run exception thrown by the application through the implementation of + * {@link com.amazonaws.serverless.proxy.ExceptionHandler}. When this parameter is set to false the Lambda + * container handler object lets the Exception propagate upwards to the Lambda handler class. + * @return true if exception mapping is disabled, false otherwise. + */ + public boolean isDisableExceptionMapper() { + return disableExceptionMapper; + } + + /** + * This configuration parameter tells the container whether it should skip exception mapping and simply let any + * Exception thrown by the underlying application bubble up to the Lambda handler class. + * @param disable Set this value to true to disable exception mapping, false otherwise. + */ + public void setDisableExceptionMapper(boolean disable) { + this.disableExceptionMapper = disable; + } } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandlerTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandlerTest.java new file mode 100644 index 000000000..8b10520c3 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandlerTest.java @@ -0,0 +1,105 @@ +package com.amazonaws.serverless.proxy.internal; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.*; +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +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.apache.http.impl.execchain.RequestAbortedException; +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; +import java.util.concurrent.CountDownLatch; + +import static org.junit.Assert.*; + +public class LambdaContainerHandlerTest { + private boolean isRuntimeException = false; + private boolean throwException = false; + + ExceptionContainerHandlerTest handler = new ExceptionContainerHandlerTest( + AwsProxyRequest.class, AwsProxyResponse.class, + new AwsProxyHttpServletRequestReader(), new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler(), new InitializationWrapper() + ); + + @Test + public void throwRuntime_returnsUnwrappedException() { + try { + isRuntimeException = true; + throwException = true; + LambdaContainerHandler.getContainerConfig().setDisableExceptionMapper(true); + handler.proxy(new AwsProxyRequestBuilder("/test", "GET").build(), new MockLambdaContext()); + } catch (Exception e) { + assertNotNull(e); + assertEquals(ExceptionContainerHandlerTest.RUNTIME_MESSAGE, e.getMessage()); + return; + } + fail("Did not throw runtime exception"); + } + + @Test + public void throwNonRuntime_returnsWrappedException() { + try { + isRuntimeException = false; + throwException = true; + LambdaContainerHandler.getContainerConfig().setDisableExceptionMapper(true); + handler.proxy(new AwsProxyRequestBuilder("/test", "GET").build(), new MockLambdaContext()); + } catch (Exception e) { + assertNotNull(e); + assertNotNull(e.getCause()); + assertTrue(e.getCause() instanceof RequestAbortedException); + assertEquals(ExceptionContainerHandlerTest.NON_RUNTIME_MESSAGE, e.getCause().getMessage()); + return; + } + fail("Did not throw exception"); + } + + @Test + public void noException_returnsResponse() { + throwException = false; + LambdaContainerHandler.getContainerConfig().setDisableExceptionMapper(false); + AwsProxyResponse resp = handler.proxy(new AwsProxyRequestBuilder("/test", "GET").build(), new MockLambdaContext()); + assertEquals(200, resp.getStatusCode()); + assertEquals("OK", resp.getBody()); + } + + public class ExceptionContainerHandlerTest extends LambdaContainerHandler { + + public static final String RUNTIME_MESSAGE = "test RuntimeException"; + public static final String NON_RUNTIME_MESSAGE = "test NonRuntimeException"; + + protected ExceptionContainerHandlerTest(Class requestClass, Class responseClass, RequestReader requestReader, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, InitializationWrapper init) { + super(requestClass, responseClass, requestReader, responseWriter, securityContextWriter, exceptionHandler, init); + } + + @Override + protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { + return new AwsHttpServletResponse(request, latch); + } + + @Override + protected void handleRequest(HttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + if (throwException) { + if (isRuntimeException) { + throw new RuntimeException(RUNTIME_MESSAGE); + } else { + throw new RequestAbortedException(NON_RUNTIME_MESSAGE); + } + } + containerResponse.setStatus(200); + containerResponse.getWriter().print("OK"); + containerResponse.flushBuffer(); + } + + @Override + public void initialize() throws ContainerInitializationException { + + } + } +} From 50166c3e6fbcdefdadd3faf01dfb2a9cee3b9c47 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 7 Apr 2020 12:54:48 -0700 Subject: [PATCH 35/41] fix: Fixed spotbugs issue in RuntimeException cast --- .../serverless/proxy/internal/LambdaContainerHandler.java | 2 +- 1 file changed, 1 insertion(+), 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 a3a657e71..d31ae257b 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 @@ -224,7 +224,7 @@ public ResponseType proxy(RequestType request, Context context) { latch.countDown(); if (getContainerConfig().isDisableExceptionMapper()) { - if (RuntimeException.class.isAssignableFrom(e.getClass())) { + if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { throw new RuntimeException(e); From 2255820023a4d6abf290542741d27d6c94fa7dbd Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 7 Apr 2020 15:46:48 -0700 Subject: [PATCH 36/41] test: Added tests for more complex content types mentioned in issue #315 --- .../internal/testutils/AwsProxyRequestBuilder.java | 2 +- .../serverless/proxy/spring/ServletAppTest.java | 12 ++++++++++++ .../proxy/spring/servletapp/MessageController.java | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) 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 0e3fb816a..8d4dc63f2 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 @@ -258,7 +258,7 @@ public AwsProxyRequestBuilder nullBody() { } public AwsProxyRequestBuilder body(Object body) { - if (request.getMultiValueHeaders() != null && request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).equals(MediaType.APPLICATION_JSON)) { + if (request.getMultiValueHeaders() != null && request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON)) { try { return body(LambdaContainerHandler.getObjectMapper().writeValueAsString(body)); } catch (JsonProcessingException e) { 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 ac610d2a1..88cf735dc 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 @@ -88,6 +88,18 @@ public void messageObject_parsesObject_returnsCorrectMessage() { assertEquals("test message", resp.getBody()); } + @Test + public void messageObject_propertiesInContentType_returnsCorrectMessage() { + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/message", "POST") + .header(HttpHeaders.CONTENT_TYPE, "application/json;v=1") + .header(HttpHeaders.ACCEPT, "application/json;v=1") + .body(new MessageData("test message")); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + assertEquals("test message", resp.getBody()); + } + @Test public void echoMessage_fileNameLikeParameter_returnsMessage() { AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/echo/test.test.test", "GET"); 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 a03846f39..d46806b61 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 @@ -24,7 +24,7 @@ public ResponseEntity validateBody(@RequestBody @Valid UserData userData return ResponseEntity.ok(VALID_MESSAGE); } - @RequestMapping(path="/message", method = RequestMethod.POST, produces={"text/plain"}, consumes = {"application/json"}) + @RequestMapping(path="/message", method = RequestMethod.POST) public String returnMessage(@RequestBody MessageData data) { if (data == null) { throw new RuntimeException("No message data"); From 3971c2feb503e06d0d928dd6a7925aa2a90a92c3 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 7 Apr 2020 20:45:22 -0700 Subject: [PATCH 37/41] docs: Updated samples to support SAM CLI operations out of the box to address #293 and switched to HTTP API by default --- .gitignore | 4 ++ gha_build.sh | 12 ---- samples/jersey/pet-store/README.md | 68 ++++++------------- .../pet-store/{sam.yaml => template.yml} | 12 ++-- samples/spark/pet-store/README.md | 68 ++++++------------- .../pet-store/{sam.yaml => template.yml} | 12 ++-- samples/spring/pet-store/README.md | 68 ++++++------------- .../pet-store/{sam.yaml => template.yml} | 12 ++-- samples/springboot/pet-store/README.md | 68 ++++++------------- samples/springboot/pet-store/build.gradle | 5 +- .../pet-store/{sam.yaml => template.yml} | 12 ++-- samples/springboot2/pet-store/README.md | 68 ++++++------------- samples/springboot2/pet-store/build.gradle | 3 - .../filter/CognitoIdentityFilter.java | 1 + .../pet-store/{sam.yaml => template.yml} | 12 ++-- samples/struts2/pet-store/README.md | 66 ++++++------------ .../pet-store/{sam.yaml => template.yml} | 12 ++-- 17 files changed, 167 insertions(+), 336 deletions(-) rename samples/jersey/pet-store/{sam.yaml => template.yml} (71%) rename samples/spark/pet-store/{sam.yaml => template.yml} (71%) rename samples/spring/pet-store/{sam.yaml => template.yml} (71%) rename samples/springboot/pet-store/{sam.yaml => template.yml} (72%) rename samples/springboot2/pet-store/{sam.yaml => template.yml} (72%) rename samples/struts2/pet-store/{sam.yaml => template.yml} (72%) diff --git a/.gitignore b/.gitignore index 870a2d42f..8f6b9d359 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,7 @@ gradlew* # Exclude maven wrapper !/.mvn/wrapper/maven-wrapper.jar + +# SAM files +samconfig.toml +.aws-sam/ diff --git a/gha_build.sh b/gha_build.sh index d4d2d7237..9867bf509 100755 --- a/gha_build.sh +++ b/gha_build.sh @@ -80,18 +80,6 @@ function sample { if [[ "$?" -ne 0 ]]; then exit 1 fi - - SAM_FILE=${SAMPLE_FOLDER}/sam.yaml - if [[ -f "$SAM_FILE" ]]; then - TARGET_ZIP=$(cat ${SAM_FILE} | grep CodeUri | sed -e 's/^.*:\ //g') - if [[ ! -f "${SAMPLE_FOLDER}/${TARGET_ZIP}" ]]; then - echo "COULD NOT FIND TARGET ZIP FILE $TARGET_ZIP FOR $1 SAMPLE" - exit 1 - fi - else - echo "COULD NOT FIND SAM FILE: '${SAM_FILE}'" - exit 1 - fi } # set up the master pom otherwise we won't be able to find new dependencies diff --git a/samples/jersey/pet-store/README.md b/samples/jersey/pet-store/README.md index bfb441648..d85d56284 100644 --- a/samples/jersey/pet-store/README.md +++ b/samples/jersey/pet-store/README.md @@ -1,62 +1,36 @@ # Serverless Jersey example -A basic pet store written with the [Jersey framework](https://jersey.java.net/). The `LambdaHandler` object is the main entry point for Lambda. +A basic pet store written with the [Jersey framework](https://jersey.java.net/). The `StreamLambdaHandler` object is the main entry point for Lambda. -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition. -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. +## Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) -In a shell, navigate to the sample's folder and use maven to build a deployable jar. +## Deployment +In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package ``` -$ mvn package +$ sam build ``` -This command should generate a `serverless-jersey-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. +This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen ``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name +$ sam deploy --guided ``` -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessJerseySample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `JerseyPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessJerseySample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written in jersey with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "JerseyPetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "JerseySample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +--------------------------------------------------------------------------------------------------------- -Copy the `OutputValue` into a browser to test a first request. \ No newline at end of file +$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +``` \ No newline at end of file diff --git a/samples/jersey/pet-store/sam.yaml b/samples/jersey/pet-store/template.yml similarity index 71% rename from samples/jersey/pet-store/sam.yaml rename to samples/jersey/pet-store/template.yml index 7ca060dae..ababb78a8 100644 --- a/samples/jersey/pet-store/sam.yaml +++ b/samples/jersey/pet-store/template.yml @@ -13,20 +13,20 @@ Resources: Properties: Handler: com.amazonaws.serverless.sample.jersey.StreamLambdaHandler::handleRequest Runtime: java8 - CodeUri: target/serverless-jersey-example-1.0-SNAPSHOT-lambda-package.zip + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 20 Events: - GetResource: - Type: Api + HttpApiEvent: + Type: HttpApi Properties: - Path: /{proxy+} - Method: any + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' Outputs: JerseyPetStoreApi: Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' Export: Name: JerseyPetStoreApi diff --git a/samples/spark/pet-store/README.md b/samples/spark/pet-store/README.md index 5d3e34d5a..2bfec99de 100644 --- a/samples/spark/pet-store/README.md +++ b/samples/spark/pet-store/README.md @@ -1,62 +1,36 @@ # Serverless Spark example -A basic pet store written with the [Spark framework](http://sparkjava.com/). The `LambdaHandler` object is the main entry point for Lambda. +A basic pet store written with the [Spark framework](http://sparkjava.com/). The `StreamLambdaHandler` object is the main entry point for Lambda. -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition. -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. +## Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) -In a shell, navigate to the sample's folder and use maven to build a deployable jar. +## Deployment +In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package ``` -$ mvn package +$ sam build ``` -This command should generate a `serverless-spark-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. +This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen ``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name +$ sam deploy --guided ``` -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSparkSample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SparkPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSparkSample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/SparkSample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written with spark with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "PetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "SparkSample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +--------------------------------------------------------------------------------------------------------- -Copy the `OutputValue` into a browser to test a first request. +$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +``` \ No newline at end of file diff --git a/samples/spark/pet-store/sam.yaml b/samples/spark/pet-store/template.yml similarity index 71% rename from samples/spark/pet-store/sam.yaml rename to samples/spark/pet-store/template.yml index b16086533..26e0a6535 100644 --- a/samples/spark/pet-store/sam.yaml +++ b/samples/spark/pet-store/template.yml @@ -13,20 +13,20 @@ Resources: Properties: Handler: com.amazonaws.serverless.sample.spark.StreamLambdaHandler::handleRequest Runtime: java8 - CodeUri: target/serverless-spark-example-1.0-SNAPSHOT-lambda-package.zip + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 20 Events: - GetResource: - Type: Api + HttpApiEvent: + Type: HttpApi Properties: - Path: /{proxy+} - Method: any + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' Outputs: SparkPetStoreApi: Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' Export: Name: SparkPetStoreApi diff --git a/samples/spring/pet-store/README.md b/samples/spring/pet-store/README.md index 17592941b..5637b6b69 100644 --- a/samples/spring/pet-store/README.md +++ b/samples/spring/pet-store/README.md @@ -1,62 +1,36 @@ # Serverless Spring example -A basic pet store written with the [Spring framework](https://projects.spring.io/spring-framework/). The `LambdaHandler` object is the main entry point for Lambda. +A basic pet store written with the [Spring framework](https://projects.spring.io/spring-framework/). The `StreamLambdaHandler` object is the main entry point for Lambda. -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition. -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. +## Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) -In a shell, navigate to the sample's folder and use maven to build a deployable jar. +## Deployment +In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package ``` -$ mvn package +$ sam build ``` -This command should generate a `serverless-spring-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. +This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen ``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name +$ sam deploy --guided ``` -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringSample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SparkPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringSample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written with spark with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "PetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "JerseySample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +--------------------------------------------------------------------------------------------------------- -Copy the `OutputValue` into a browser to test a first request. +$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +``` \ No newline at end of file diff --git a/samples/spring/pet-store/sam.yaml b/samples/spring/pet-store/template.yml similarity index 71% rename from samples/spring/pet-store/sam.yaml rename to samples/spring/pet-store/template.yml index 6a6f07e47..8c3dec021 100644 --- a/samples/spring/pet-store/sam.yaml +++ b/samples/spring/pet-store/template.yml @@ -13,20 +13,20 @@ Resources: Properties: Handler: com.amazonaws.serverless.sample.spring.StreamLambdaHandler::handleRequest Runtime: java8 - CodeUri: target/serverless-spring-example-1.0-SNAPSHOT-lambda-package.zip + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 30 Events: - GetResource: - Type: Api + HttpApiEvent: + Type: HttpApi Properties: - Path: /{proxy+} - Method: any + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' Outputs: SpringPetStoreApi: Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' Export: Name: SpringPetStoreApi diff --git a/samples/springboot/pet-store/README.md b/samples/springboot/pet-store/README.md index ae9100cb3..4462f1592 100644 --- a/samples/springboot/pet-store/README.md +++ b/samples/springboot/pet-store/README.md @@ -1,62 +1,36 @@ # Serverless Spring Boot example -A basic pet store written with the [Spring Boot framework](https://projects.spring.io/spring-boot/). The `LambdaHandler` object is the main entry point for Lambda. +A basic pet store written with the [Spring Boot framework](https://projects.spring.io/spring-boot/). The `StreamLambdaHandler` object is the main entry point for Lambda. -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition. -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. +## Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) -In a shell, navigate to the sample's folder and use maven to build a deployable jar. +## Deployment +In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package ``` -$ mvn package +$ sam build ``` -This command should generate a `serverless-spring-boot-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. +This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen ``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name +$ sam deploy --guided ``` -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringBootSample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SpringBootPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringBootSample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written with spark with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "SpringBootPetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "JerseySample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +--------------------------------------------------------------------------------------------------------- -Copy the `OutputValue` into a browser to test a first request. +$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +``` \ No newline at end of file diff --git a/samples/springboot/pet-store/build.gradle b/samples/springboot/pet-store/build.gradle index 53295266a..caaeea89c 100644 --- a/samples/springboot/pet-store/build.gradle +++ b/samples/springboot/pet-store/build.gradle @@ -1,6 +1,3 @@ -plugins { - id 'org.springframework.boot' version '1.5.22.RELEASE' -} apply plugin: 'java' repositories { @@ -11,7 +8,7 @@ repositories { dependencies { compile ( - 'org.springframework.boot:spring-boot-starter-web', + 'org.springframework.boot:spring-boot-starter-web:1.5.22.RELEASE', 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', 'io.symphonia:lambda-logging:1.0.1' ) diff --git a/samples/springboot/pet-store/sam.yaml b/samples/springboot/pet-store/template.yml similarity index 72% rename from samples/springboot/pet-store/sam.yaml rename to samples/springboot/pet-store/template.yml index 23e143a95..de08aa74b 100644 --- a/samples/springboot/pet-store/sam.yaml +++ b/samples/springboot/pet-store/template.yml @@ -13,20 +13,20 @@ Resources: Properties: Handler: com.amazonaws.serverless.sample.springboot.StreamLambdaHandler::handleRequest Runtime: java8 - CodeUri: target/serverless-spring-boot-example-1.0-SNAPSHOT-lambda-package.zip + CodeUri: . MemorySize: 1512 Policies: AWSLambdaBasicExecutionRole Timeout: 60 Events: - GetResource: - Type: Api + HttpApiEvent: + Type: HttpApi Properties: - Path: /{proxy+} - Method: any + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' Outputs: SpringBootPetStoreApi: Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' Export: Name: SpringBootPetStoreApi diff --git a/samples/springboot2/pet-store/README.md b/samples/springboot2/pet-store/README.md index 5f8edb68b..8e15b3773 100644 --- a/samples/springboot2/pet-store/README.md +++ b/samples/springboot2/pet-store/README.md @@ -1,62 +1,36 @@ # Serverless Spring Boot 2 example -A basic pet store written with the [Spring Boot 2 framework](https://projects.spring.io/spring-boot/). The `LambdaHandler` object is the main entry point for Lambda. +A basic pet store written with the [Spring Boot 2 framework](https://projects.spring.io/spring-boot/). The `StreamLambdaHandler` object is the main entry point for Lambda. -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition. -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. +## Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) -In a shell, navigate to the sample's folder and use maven to build a deployable jar. +## Deployment +In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package ``` -$ mvn package +$ sam build ``` -This command should generate a `serverless-spring-boot-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. +This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen ``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name +$ sam deploy --guided ``` -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringBootSample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SpringBootPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringBootSample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written with spark with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "SpringBootPetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "JerseySample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +--------------------------------------------------------------------------------------------------------- -Copy the `OutputValue` into a browser to test a first request. +$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +``` \ No newline at end of file diff --git a/samples/springboot2/pet-store/build.gradle b/samples/springboot2/pet-store/build.gradle index 10480c9a4..365f3ac22 100644 --- a/samples/springboot2/pet-store/build.gradle +++ b/samples/springboot2/pet-store/build.gradle @@ -1,6 +1,3 @@ -plugins { - id 'org.springframework.boot' version '2.2.6.RELEASE' -} apply plugin: 'java' repositories { diff --git a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/filter/CognitoIdentityFilter.java b/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/filter/CognitoIdentityFilter.java index 3097bb80b..5c3482e40 100644 --- a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/filter/CognitoIdentityFilter.java +++ b/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/filter/CognitoIdentityFilter.java @@ -41,6 +41,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo if (apiGwContext == null) { log.warn("API Gateway context is null"); filterChain.doFilter(servletRequest, servletResponse); + return; } if (!AwsProxyRequestContext.class.isAssignableFrom(apiGwContext.getClass())) { log.warn("API Gateway context object is not of valid type"); diff --git a/samples/springboot2/pet-store/sam.yaml b/samples/springboot2/pet-store/template.yml similarity index 72% rename from samples/springboot2/pet-store/sam.yaml rename to samples/springboot2/pet-store/template.yml index c00d45c3f..a82974790 100644 --- a/samples/springboot2/pet-store/sam.yaml +++ b/samples/springboot2/pet-store/template.yml @@ -13,20 +13,20 @@ Resources: Properties: Handler: com.amazonaws.serverless.sample.springboot2.StreamLambdaHandler::handleRequest Runtime: java8 - CodeUri: target/serverless-springboot2-example-1.0-SNAPSHOT-lambda-package.zip + CodeUri: . MemorySize: 1512 Policies: AWSLambdaBasicExecutionRole Timeout: 60 Events: - GetResource: - Type: Api + HttpApiEvent: + Type: HttpApi Properties: - Path: /{proxy+} - Method: any + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' Outputs: SpringBootPetStoreApi: Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' Export: Name: SpringBootPetStoreApi diff --git a/samples/struts2/pet-store/README.md b/samples/struts2/pet-store/README.md index 9203dd896..89cc1676b 100644 --- a/samples/struts2/pet-store/README.md +++ b/samples/struts2/pet-store/README.md @@ -1,62 +1,36 @@ # Serverless Struts2 example A basic pet store written with the [Apache Struts framework](https://struts.apache.org). The `Struts2LambdaHandler` object provided by the `aws-serverless-java-container-struts2` is the main entry point for Lambda. -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. +## Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) -In a shell, navigate to the sample's folder and use maven to build a deployable jar. +## Deployment +In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package ``` -$ mvn package +$ sam build ``` -This command should generate a `serverless-struts-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the zip file, we can use the AWS CLI to package the template for deployment. +This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen ``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name +$ sam deploy --guided ``` -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringSample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SparkPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringSample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written with Apache Struts with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "PetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "StrutsSample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +--------------------------------------------------------------------------------------------------------- -Copy the `OutputValue` into a browser to test a first request. +$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +``` \ No newline at end of file diff --git a/samples/struts2/pet-store/sam.yaml b/samples/struts2/pet-store/template.yml similarity index 72% rename from samples/struts2/pet-store/sam.yaml rename to samples/struts2/pet-store/template.yml index c44aa3201..72128dd5b 100644 --- a/samples/struts2/pet-store/sam.yaml +++ b/samples/struts2/pet-store/template.yml @@ -13,20 +13,20 @@ Resources: Properties: Handler: com.amazonaws.serverless.proxy.struts2.Struts2LambdaHandler::handleRequest Runtime: java8 - CodeUri: target/serverless-struts-example-1.0-SNAPSHOT-lambda.zip + CodeUri: . MemorySize: 256 Policies: AWSLambdaBasicExecutionRole Timeout: 30 Events: - GetResource: - Type: Api + HttpApiEvent: + Type: HttpApi Properties: - Path: /{proxy+} - Method: any + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' Outputs: SpringPetStoreApi: Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' Export: Name: Struts2PetStoreApi From 795f3a3bba9ab5e0577d0218b464385d03b4cc1c Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 7 Apr 2020 22:12:25 -0700 Subject: [PATCH 38/41] feat: Updated archetypes to work out of the box with the SAM CLI, continuing to address #293 --- .../META-INF/maven/archetype-metadata.xml | 2 +- .../resources/archetype-resources/README.md | 95 +++++++----------- .../{sam.yaml => template.yml} | 4 +- .../META-INF/maven/archetype-metadata.xml | 2 +- .../resources/archetype-resources/README.md | 97 +++++++------------ .../{sam.yaml => template.yml} | 4 +- .../META-INF/maven/archetype-metadata.xml | 2 +- .../resources/archetype-resources/README.md | 97 +++++++------------ .../{sam.yaml => template.yml} | 4 +- .../META-INF/maven/archetype-metadata.xml | 2 +- .../resources/archetype-resources/README.md | 97 +++++++------------ .../archetype-resources/build.gradle | 5 +- .../{sam.yaml => template.yml} | 4 +- .../META-INF/maven/archetype-metadata.xml | 2 +- .../resources/archetype-resources/README.md | 97 +++++++------------ .../archetype-resources/build.gradle | 3 - .../{sam.yaml => template.yml} | 4 +- .../META-INF/maven/archetype-metadata.xml | 2 +- .../resources/archetype-resources/README.md | 97 +++++++------------ .../{sam.yaml => template.yml} | 4 +- 20 files changed, 222 insertions(+), 402 deletions(-) rename aws-serverless-jersey-archetype/src/main/resources/archetype-resources/{sam.yaml => template.yml} (94%) rename aws-serverless-spark-archetype/src/main/resources/archetype-resources/{sam.yaml => template.yml} (94%) rename aws-serverless-spring-archetype/src/main/resources/archetype-resources/{sam.yaml => template.yml} (94%) rename aws-serverless-springboot-archetype/src/main/resources/archetype-resources/{sam.yaml => template.yml} (94%) rename aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/{sam.yaml => template.yml} (94%) rename aws-serverless-struts2-archetype/src/main/resources/archetype-resources/{sam.yaml => template.yml} (95%) diff --git a/aws-serverless-jersey-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/aws-serverless-jersey-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml index b934c4a4a..dc8cb6204 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ b/aws-serverless-jersey-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -24,7 +24,7 @@ - sam.yaml + template.yml README.md build.gradle diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/README.md index de5f4671a..1dc39a0a2 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/README.md +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/README.md @@ -20,37 +20,40 @@ The \${artifactId} project, created with [`aws-serverless-java-container`](https The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. -The project folder also includes a `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). +The project folder also includes a `template.yml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with the [SAM CLI](https://github.com/awslabs/aws-sam-cli). -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. +#[[##]]# Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) + +#[[##]]# Building the project +You can use the SAM CLI to quickly build the project ```bash $ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-jersey-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false $ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` +$ sam build +Building resource '\${resourceName}Function' +Running JavaGradleWorkflow:GradleBuild +Running JavaGradleWorkflow:CopyArtifacts -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. +Build Succeeded -First, install SAM local: +Built Artifacts : .aws-sam/build +Built Template : .aws-sam/build/template.yaml -```bash -$ npm install -g aws-sam-local +Commands you can use next +========================= +[*] Invoke Function: sam local invoke +[*] Deploy: sam deploy --guided ``` -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. +#[[##]]# Testing locally with the SAM CLI + +From the project root folder - where the `template.yml` file is located - start the API with the SAM CLI. ```bash -$ sam local start-api --template sam.yaml +$ sam local start-api ... Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] @@ -67,54 +70,22 @@ $ curl -s http://127.0.0.1:3000/ping | python -m json.tool } ``` -## Deploying to AWS -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` +#[[##]]# Deploying to AWS +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - ``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessJerseyApi --capabilities CAPABILITY_IAM +$ sam deploy --guided ``` -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessJerseyApi` key of the `Outputs` property: +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` -$ aws cloudformation describe-stacks --stack-name ServerlessJerseyApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessJerseyApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Jersey API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessJerseyApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - +... +------------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +------------------------------------------------------------------------------------------------------------- +\${resourceName}Api - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets +------------------------------------------------------------------------------------------------------------- ``` Copy the `OutputValue` into a browser or use curl to test your first request: diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/sam.yaml b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/template.yml similarity index 94% rename from aws-serverless-jersey-archetype/src/main/resources/archetype-resources/sam.yaml rename to aws-serverless-jersey-archetype/src/main/resources/archetype-resources/template.yml index 9df80ae88..db6ecbaff 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/sam.yaml +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/template.yml @@ -33,12 +33,12 @@ Resources: Properties: Handler: ${groupId}.StreamLambdaHandler::handleRequest Runtime: java8 - CodeUri: target/${artifactId}-${version}-lambda-package.zip + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 15 Events: - GetResource: + ProxyResource: Type: Api Properties: Path: /{proxy+} diff --git a/aws-serverless-spark-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/aws-serverless-spark-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml index 45dc2c1a7..2ea9dca13 100644 --- a/aws-serverless-spark-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ b/aws-serverless-spark-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -24,7 +24,7 @@ - sam.yaml + template.yml README.md build.gradle diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/README.md index 82a59465b..1dc39a0a2 100644 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/README.md +++ b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/README.md @@ -20,37 +20,40 @@ The \${artifactId} project, created with [`aws-serverless-java-container`](https The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. -The project folder also includes a `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). +The project folder also includes a `template.yml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with the [SAM CLI](https://github.com/awslabs/aws-sam-cli). -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. +#[[##]]# Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) + +#[[##]]# Building the project +You can use the SAM CLI to quickly build the project ```bash -$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-spark-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false +$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-jersey-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false $ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` +$ sam build +Building resource '\${resourceName}Function' +Running JavaGradleWorkflow:GradleBuild +Running JavaGradleWorkflow:CopyArtifacts -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. +Build Succeeded -First, install SAM local: +Built Artifacts : .aws-sam/build +Built Template : .aws-sam/build/template.yaml -```bash -$ npm install -g aws-sam-local +Commands you can use next +========================= +[*] Invoke Function: sam local invoke +[*] Deploy: sam deploy --guided ``` -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. +#[[##]]# Testing locally with the SAM CLI + +From the project root folder - where the `template.yml` file is located - start the API with the SAM CLI. ```bash -$ sam local start-api --template sam.yaml +$ sam local start-api ... Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] @@ -67,54 +70,22 @@ $ curl -s http://127.0.0.1:3000/ping | python -m json.tool } ``` -## Deploying to AWS -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` +#[[##]]# Deploying to AWS +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - ``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSparkApi --capabilities CAPABILITY_IAM +$ sam deploy --guided ``` -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessSparkApi` key of the `Outputs` property: +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` -$ aws cloudformation describe-stacks --stack-name ServerlessSparkApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessSparkApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Spark API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessSparkApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - +... +------------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +------------------------------------------------------------------------------------------------------------- +\${resourceName}Api - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets +------------------------------------------------------------------------------------------------------------- ``` Copy the `OutputValue` into a browser or use curl to test your first request: diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/sam.yaml b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/template.yml similarity index 94% rename from aws-serverless-spark-archetype/src/main/resources/archetype-resources/sam.yaml rename to aws-serverless-spark-archetype/src/main/resources/archetype-resources/template.yml index 674c769af..d9e83ceba 100644 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/sam.yaml +++ b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/template.yml @@ -33,12 +33,12 @@ Resources: Properties: Handler: ${groupId}.StreamLambdaHandler::handleRequest Runtime: java8 - CodeUri: target/${artifactId}-${version}-lambda-package.zip + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 15 Events: - GetResource: + ProxyResource: Type: Api Properties: Path: /{proxy+} diff --git a/aws-serverless-spring-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/aws-serverless-spring-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml index 9295c6457..e279efa50 100644 --- a/aws-serverless-spring-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ b/aws-serverless-spring-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -30,7 +30,7 @@ - sam.yaml + template.yml README.md build.gradle diff --git a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/README.md index 1bb929910..1dc39a0a2 100644 --- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/README.md +++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/README.md @@ -20,37 +20,40 @@ The \${artifactId} project, created with [`aws-serverless-java-container`](https The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. -The project folder also includes a `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). +The project folder also includes a `template.yml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with the [SAM CLI](https://github.com/awslabs/aws-sam-cli). -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. +#[[##]]# Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) + +#[[##]]# Building the project +You can use the SAM CLI to quickly build the project ```bash -$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-spring-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false +$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-jersey-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false $ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` +$ sam build +Building resource '\${resourceName}Function' +Running JavaGradleWorkflow:GradleBuild +Running JavaGradleWorkflow:CopyArtifacts -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. +Build Succeeded -First, install SAM local: +Built Artifacts : .aws-sam/build +Built Template : .aws-sam/build/template.yaml -```bash -$ npm install -g aws-sam-local +Commands you can use next +========================= +[*] Invoke Function: sam local invoke +[*] Deploy: sam deploy --guided ``` -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. +#[[##]]# Testing locally with the SAM CLI + +From the project root folder - where the `template.yml` file is located - start the API with the SAM CLI. ```bash -$ sam local start-api --template sam.yaml +$ sam local start-api ... Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] @@ -67,54 +70,22 @@ $ curl -s http://127.0.0.1:3000/ping | python -m json.tool } ``` -## Deploying to AWS -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` +#[[##]]# Deploying to AWS +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - ``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringApi --capabilities CAPABILITY_IAM +$ sam deploy --guided ``` -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessSpringApi` key of the `Outputs` property: +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessSpringApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Spring API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessSpringApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - +... +------------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +------------------------------------------------------------------------------------------------------------- +\${resourceName}Api - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets +------------------------------------------------------------------------------------------------------------- ``` Copy the `OutputValue` into a browser or use curl to test your first request: diff --git a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/sam.yaml b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/template.yml similarity index 94% rename from aws-serverless-spring-archetype/src/main/resources/archetype-resources/sam.yaml rename to aws-serverless-spring-archetype/src/main/resources/archetype-resources/template.yml index 374dfe593..292d87a9c 100644 --- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/sam.yaml +++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/template.yml @@ -33,12 +33,12 @@ Resources: Properties: Handler: ${groupId}.StreamLambdaHandler::handleRequest Runtime: java8 - CodeUri: target/${artifactId}-${version}-lambda-package.zip + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 15 Events: - GetResource: + ProxyResource: Type: Api Properties: Path: /{proxy+} diff --git a/aws-serverless-springboot-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/aws-serverless-springboot-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml index ec02bb6ab..5379692ba 100644 --- a/aws-serverless-springboot-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ b/aws-serverless-springboot-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -30,7 +30,7 @@ - sam.yaml + template.yml README.md build.gradle diff --git a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/README.md index 43a3683f5..1dc39a0a2 100644 --- a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/README.md +++ b/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/README.md @@ -20,37 +20,40 @@ The \${artifactId} project, created with [`aws-serverless-java-container`](https The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. -The project folder also includes a `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). +The project folder also includes a `template.yml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with the [SAM CLI](https://github.com/awslabs/aws-sam-cli). -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. +#[[##]]# Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) + +#[[##]]# Building the project +You can use the SAM CLI to quickly build the project ```bash -$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-springboot-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false +$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-jersey-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false $ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` +$ sam build +Building resource '\${resourceName}Function' +Running JavaGradleWorkflow:GradleBuild +Running JavaGradleWorkflow:CopyArtifacts -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. +Build Succeeded -First, install SAM local: +Built Artifacts : .aws-sam/build +Built Template : .aws-sam/build/template.yaml -```bash -$ npm install -g aws-sam-local +Commands you can use next +========================= +[*] Invoke Function: sam local invoke +[*] Deploy: sam deploy --guided ``` -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. +#[[##]]# Testing locally with the SAM CLI + +From the project root folder - where the `template.yml` file is located - start the API with the SAM CLI. ```bash -$ sam local start-api --template sam.yaml +$ sam local start-api ... Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] @@ -67,54 +70,22 @@ $ curl -s http://127.0.0.1:3000/ping | python -m json.tool } ``` -## Deploying to AWS -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` +#[[##]]# Deploying to AWS +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - ``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringApi --capabilities CAPABILITY_IAM +$ sam deploy --guided ``` -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessSpringApi` key of the `Outputs` property: +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessSpringApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Spring API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessSpringApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - +... +------------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +------------------------------------------------------------------------------------------------------------- +\${resourceName}Api - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets +------------------------------------------------------------------------------------------------------------- ``` Copy the `OutputValue` into a browser or use curl to test your first request: 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 53c675deb..2a95fdaf4 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,6 +1,3 @@ -plugins { - id 'org.springframework.boot' version '1.5.22.RELEASE' -} apply plugin: 'java' repositories { @@ -11,7 +8,7 @@ repositories { dependencies { compile ( - 'org.springframework.boot:spring-boot-starter-web', + 'org.springframework.boot:spring-boot-starter-web:1.5.22.RELEASE', 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', 'io.symphonia:lambda-logging:1.0.1' ) diff --git a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/sam.yaml b/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/template.yml similarity index 94% rename from aws-serverless-springboot-archetype/src/main/resources/archetype-resources/sam.yaml rename to aws-serverless-springboot-archetype/src/main/resources/archetype-resources/template.yml index 3fbd348b2..832028a20 100644 --- a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/sam.yaml +++ b/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/template.yml @@ -33,12 +33,12 @@ Resources: Properties: Handler: ${groupId}.StreamLambdaHandler::handleRequest Runtime: java8 - CodeUri: target/${artifactId}-${version}-lambda-package.zip + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 30 Events: - GetResource: + ProxyResource: Type: Api Properties: Path: /{proxy+} diff --git a/aws-serverless-springboot2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/aws-serverless-springboot2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml index ec02bb6ab..5379692ba 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ b/aws-serverless-springboot2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -30,7 +30,7 @@ - sam.yaml + template.yml README.md build.gradle diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/README.md index 3f99b3445..1dc39a0a2 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/README.md +++ b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/README.md @@ -20,37 +20,40 @@ The \${artifactId} project, created with [`aws-serverless-java-container`](https The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. -The project folder also includes a `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). +The project folder also includes a `template.yml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with the [SAM CLI](https://github.com/awslabs/aws-sam-cli). -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. +#[[##]]# Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) + +#[[##]]# Building the project +You can use the SAM CLI to quickly build the project ```bash -$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-springboot2-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false +$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-jersey-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false $ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` +$ sam build +Building resource '\${resourceName}Function' +Running JavaGradleWorkflow:GradleBuild +Running JavaGradleWorkflow:CopyArtifacts -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. +Build Succeeded -First, install SAM local: +Built Artifacts : .aws-sam/build +Built Template : .aws-sam/build/template.yaml -```bash -$ npm install -g aws-sam-local +Commands you can use next +========================= +[*] Invoke Function: sam local invoke +[*] Deploy: sam deploy --guided ``` -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. +#[[##]]# Testing locally with the SAM CLI + +From the project root folder - where the `template.yml` file is located - start the API with the SAM CLI. ```bash -$ sam local start-api --template sam.yaml +$ sam local start-api ... Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] @@ -67,54 +70,22 @@ $ curl -s http://127.0.0.1:3000/ping | python -m json.tool } ``` -## Deploying to AWS -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` +#[[##]]# Deploying to AWS +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - ``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringApi --capabilities CAPABILITY_IAM +$ sam deploy --guided ``` -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessSpringApi` key of the `Outputs` property: +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessSpringApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Spring API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessSpringApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - +... +------------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +------------------------------------------------------------------------------------------------------------- +\${resourceName}Api - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets +------------------------------------------------------------------------------------------------------------- ``` Copy the `OutputValue` into a browser or use curl to test your first request: 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 2c95f91d0..66ba6d9a2 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 @@ -1,6 +1,3 @@ -plugins { - id 'org.springframework.boot' version '2.1.1.RELEASE' -} apply plugin: 'java' repositories { diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/sam.yaml b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/template.yml similarity index 94% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/sam.yaml rename to aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/template.yml index 970025c41..ae2adf28c 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/sam.yaml +++ b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/template.yml @@ -33,12 +33,12 @@ Resources: Properties: Handler: ${groupId}.StreamLambdaHandler::handleRequest Runtime: java8 - CodeUri: target/${artifactId}-${version}-lambda-package.zip + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 30 Events: - GetResource: + ProxyResource: Type: Api Properties: Path: /{proxy+} diff --git a/aws-serverless-struts2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/aws-serverless-struts2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml index 91a53e562..ad8b86248 100644 --- a/aws-serverless-struts2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ b/aws-serverless-struts2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -31,7 +31,7 @@ - sam.yaml + template.yml README.md build.gradle diff --git a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/README.md index 7be306683..1dc39a0a2 100644 --- a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/README.md +++ b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/README.md @@ -20,37 +20,40 @@ The \${artifactId} project, created with [`aws-serverless-java-container`](https The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. -The project folder also includes a `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). +The project folder also includes a `template.yml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with the [SAM CLI](https://github.com/awslabs/aws-sam-cli). -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. +#[[##]]# Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) + +#[[##]]# Building the project +You can use the SAM CLI to quickly build the project ```bash -$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-struts2-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false +$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-jersey-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false $ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` +$ sam build +Building resource '\${resourceName}Function' +Running JavaGradleWorkflow:GradleBuild +Running JavaGradleWorkflow:CopyArtifacts -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. +Build Succeeded -First, install SAM local: +Built Artifacts : .aws-sam/build +Built Template : .aws-sam/build/template.yaml -```bash -$ npm install -g aws-sam-local +Commands you can use next +========================= +[*] Invoke Function: sam local invoke +[*] Deploy: sam deploy --guided ``` -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. +#[[##]]# Testing locally with the SAM CLI + +From the project root folder - where the `template.yml` file is located - start the API with the SAM CLI. ```bash -$ sam local start-api --template sam.yaml +$ sam local start-api ... Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] @@ -67,54 +70,22 @@ $ curl -s http://127.0.0.1:3000/ping | python -m json.tool } ``` -## Deploying to AWS -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` +#[[##]]# Deploying to AWS +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - ``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringApi --capabilities CAPABILITY_IAM +$ sam deploy --guided ``` -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessSpringApi` key of the `Outputs` property: +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessSpringApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Spring API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessSpringApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - +... +------------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +------------------------------------------------------------------------------------------------------------- +\${resourceName}Api - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets +------------------------------------------------------------------------------------------------------------- ``` Copy the `OutputValue` into a browser or use curl to test your first request: diff --git a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/sam.yaml b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/template.yml similarity index 95% rename from aws-serverless-struts2-archetype/src/main/resources/archetype-resources/sam.yaml rename to aws-serverless-struts2-archetype/src/main/resources/archetype-resources/template.yml index 023266fc7..11e06d07c 100644 --- a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/sam.yaml +++ b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/template.yml @@ -33,12 +33,12 @@ Resources: Properties: Handler: com.amazonaws.serverless.proxy.struts2.Struts2LambdaHandler::handleRequest Runtime: java8 - CodeUri: target/${artifactId}-${version}-lambda.zip + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 30 Events: - GetResource: + ProxyResource: Type: Api Properties: Path: /{proxy+} From 0e4d254b96749107fb154853fb13e8f835d4c755 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 8 Apr 2020 08:49:01 -0700 Subject: [PATCH 39/41] chore: License header pass on the entire project --- .../proxy/AsyncInitializationWrapper.java | 12 ++++++++++++ .../proxy/AwsHttpApiV2SecurityContextWriter.java | 12 ++++++++++++ .../serverless/proxy/InitializationWrapper.java | 12 ++++++++++++ .../amazonaws/serverless/proxy/LogFormatter.java | 14 ++++++++++++-- .../serverless/proxy/internal/SecurityUtils.java | 14 ++++++++++++-- .../jaxrs/AwsHttpApiV2SecurityContext.java | 12 ++++++++++++ .../servlet/ApacheCombinedServletLogFormatter.java | 14 ++++++++++++-- .../proxy/internal/servlet/AwsAsyncContext.java | 13 ++++++++++++- .../AwsHttpApiV2HttpServletRequestReader.java | 12 ++++++++++++ .../AwsHttpApiV2ProxyHttpServletRequest.java | 12 ++++++++++++ .../servlet/AwsHttpServletRequestWrapper.java | 12 ++++++++++++ .../proxy/internal/servlet/AwsHttpSession.java | 13 ++++++++++++- .../servlet/AwsProxyRequestDispatcher.java | 14 ++++++++++++-- .../internal/servlet/AwsServletInputStream.java | 12 ++++++++++++ .../internal/servlet/AwsServletRegistration.java | 12 ++++++++++++ .../ServletLambdaContainerHandlerBuilder.java | 12 ++++++++++++ .../serverless/proxy/internal/testutils/Timer.java | 14 ++++++++++++-- .../serverless/proxy/model/AlbContext.java | 13 ++++++++++++- .../serverless/proxy/model/ContainerConfig.java | 14 ++++++++++++-- .../amazonaws/serverless/proxy/model/Headers.java | 12 ++++++++++++ .../proxy/model/HttpApiV2AuthorizerMap.java | 12 ++++++++++++ .../proxy/model/HttpApiV2HttpContext.java | 12 ++++++++++++ .../proxy/model/HttpApiV2JwtAuthorizer.java | 12 ++++++++++++ .../proxy/model/HttpApiV2ProxyRequest.java | 12 ++++++++++++ .../proxy/model/HttpApiV2ProxyRequestContext.java | 12 ++++++++++++ .../serverless/proxy/model/MultiValuedTreeMap.java | 14 ++++++++++++-- .../proxy/jersey/JerseyHandlerFilter.java | 14 ++++++++++++-- .../spark/embeddedserver/LambdaEmbeddedServer.java | 12 ++++++++++++ .../LambdaEmbeddedServerFactory.java | 12 ++++++++++++ .../spring/SpringBootProxyHandlerBuilder.java | 12 ++++++++++++ .../SpringBootServletConfigurationSupport.java | 12 ++++++++++++ .../proxy/spring/SpringProxyHandlerBuilder.java | 12 ++++++++++++ .../ServerlessServletEmbeddedServerFactory.java | 12 ++++++++++++ .../spring/SpringBootProxyHandlerBuilder.java | 12 ++++++++++++ ...erlessReactiveServletEmbeddedServerFactory.java | 12 ++++++++++++ .../ServerlessServletEmbeddedServerFactory.java | 13 ++++++++++++- .../struts2/Struts2LambdaContainerHandler.java | 12 ++++++++++++ .../proxy/struts2/Struts2LambdaHandler.java | 12 ++++++++++++ 38 files changed, 456 insertions(+), 20 deletions(-) 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 index c3b1d6604..813d8178e 100644 --- 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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; import com.amazonaws.serverless.exceptions.ContainerInitializationException; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsHttpApiV2SecurityContextWriter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsHttpApiV2SecurityContextWriter.java index a90c420d2..05d4ed1a2 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsHttpApiV2SecurityContextWriter.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsHttpApiV2SecurityContextWriter.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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; import com.amazonaws.serverless.proxy.internal.jaxrs.AwsHttpApiV2SecurityContext; 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 index 188a83bd3..f7c96f8f4 100644 --- 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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; import com.amazonaws.serverless.exceptions.ContainerInitializationException; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/LogFormatter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/LogFormatter.java index cef0514d5..dcd524f91 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/LogFormatter.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/LogFormatter.java @@ -1,9 +1,19 @@ +/* + * Copyright 2020 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; - import javax.ws.rs.core.SecurityContext; - /** * Implementations of the log formatter interface are used by {@link com.amazonaws.serverless.proxy.internal.LambdaContainerHandler} class to log each request * processed in the container. You can set the log formatter using the {@link com.amazonaws.serverless.proxy.internal.LambdaContainerHandler#setLogFormatter(LogFormatter)} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/SecurityUtils.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/SecurityUtils.java index 1d39d849b..3395b56c6 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/SecurityUtils.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/SecurityUtils.java @@ -1,6 +1,17 @@ +/* + * Copyright 2020 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.internal; - import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,7 +22,6 @@ import java.util.Locale; import java.util.Set; - /** * This class contains utility methods to address FSB security issues found in the application, such as string sanitization * and file path validation. diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsHttpApiV2SecurityContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsHttpApiV2SecurityContext.java index a54fd7fa5..6060fb2e2 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsHttpApiV2SecurityContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsHttpApiV2SecurityContext.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.internal.jaxrs; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatter.java index 66605aded..715e6f8d9 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatter.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ApacheCombinedServletLogFormatter.java @@ -1,6 +1,17 @@ +/* + * Copyright 2020 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.internal.servlet; - import com.amazonaws.serverless.proxy.LogFormatter; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; @@ -25,7 +36,6 @@ import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; import static java.time.temporal.ChronoField.YEAR; - /** * Default implementation of the log formatter. Based on an HttpServletRequest and HttpServletResponse implementations produced * a log line in the Apache combined log format: https://httpd.apache.org/docs/2.4/logs.html 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 57ff308cb..251624541 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 @@ -1,6 +1,17 @@ +/* + * Copyright 2020 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.internal.servlet; - import com.amazonaws.serverless.proxy.internal.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java index 2ed16dd9b..bacedbaa4 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2HttpServletRequestReader.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.internal.servlet; import com.amazonaws.serverless.exceptions.InvalidRequestEventException; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java index 8fdb55ba7..e65b506e9 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.internal.servlet; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; 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 index a8ee1840c..e151c03da 100644 --- 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.internal.servlet; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; 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 1ac0908fd..8c3df7f86 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.internal.servlet; import org.slf4j.Logger; @@ -13,7 +25,6 @@ import java.util.HashMap; import java.util.Map; - /** * This class emulates the behavior of an HTTP session. At the moment a new instance of this class * is created for each request/event. In the future, we may define a session id resolver interface 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 806ba9a49..27a352ebf 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,17 @@ +/* + * Copyright 2020 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.internal.servlet; - import com.amazonaws.serverless.proxy.internal.SecurityUtils; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; @@ -18,7 +29,6 @@ import static com.amazonaws.serverless.proxy.RequestReader.HTTP_API_EVENT_PROPERTY; import static com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest.DISPATCHER_TYPE_ATTRIBUTE; - /** * Default RequestDispatcher implementation for the AwsProxyHttpServletRequest type. A new * instance of this object is created each time a framework gets the RequestDispatcher from a servlet request. Behind diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java index a80d2e807..552e75a89 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.internal.servlet; import org.apache.commons.io.input.NullInputStream; 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 index c10b6cbfe..021503c13 100644 --- 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.internal.servlet; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 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 index 3d6e30d81..88e59fb61 100644 --- 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.internal.servlet; import com.amazonaws.serverless.exceptions.ContainerInitializationException; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/Timer.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/Timer.java index b8c332aab..b89ca934f 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/Timer.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/Timer.java @@ -1,10 +1,20 @@ +/* + * Copyright 2020 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.internal.testutils; - import java.util.LinkedHashMap; import java.util.Map; - public final class Timer { private volatile static Map timers = new LinkedHashMap<>(); private volatile static boolean enabled = false; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AlbContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AlbContext.java index 1965231be..527fc222f 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AlbContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AlbContext.java @@ -1,6 +1,17 @@ +/* + * Copyright 2020 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.model; - /*** * Context passed by ALB proxy events */ 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 6d66fd7fb..5685dbb0c 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 @@ -1,6 +1,17 @@ +/* + * Copyright 2020 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.model; - import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; import java.util.ArrayList; @@ -8,7 +19,6 @@ import java.util.HashSet; import java.util.List; - /** * Configuration parameters for the framework */ diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/Headers.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/Headers.java index 0de0d204a..73f9d2841 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/Headers.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/Headers.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.model; public class Headers extends MultiValuedTreeMap { diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java index 066fcd4a4..e7017f60f 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.model; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2HttpContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2HttpContext.java index 673199d28..e6eb12876 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2HttpContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2HttpContext.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.model; public class HttpApiV2HttpContext { diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2JwtAuthorizer.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2JwtAuthorizer.java index 28afa6b05..d81a3c77f 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2JwtAuthorizer.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2JwtAuthorizer.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.model; import java.util.List; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequest.java index 904f10413..6eee7dc4d 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequest.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.model; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java index 23d291645..55bbe1f8f 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestContext.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.model; public class HttpApiV2ProxyRequestContext { diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMap.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMap.java index b45463b9e..feeab6f2b 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMap.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMap.java @@ -1,6 +1,17 @@ +/* + * Copyright 2020 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.model; - import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import javax.ws.rs.core.MultivaluedMap; @@ -14,7 +25,6 @@ import java.util.Set; import java.util.TreeMap; - /** * Simple implementation of a multi valued tree map to use for case-insensitive headers * diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyHandlerFilter.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyHandlerFilter.java index cd6aa3e93..27e3157f0 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyHandlerFilter.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyHandlerFilter.java @@ -1,6 +1,17 @@ +/* + * Copyright 2020 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.jersey; - import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.jersey.suppliers.AwsProxyServletRequestSupplier; @@ -40,7 +51,6 @@ import static com.amazonaws.serverless.proxy.RequestReader.JAX_SECURITY_CONTEXT_PROPERTY; import static com.amazonaws.serverless.proxy.RequestReader.LAMBDA_CONTEXT_PROPERTY; - /** * Servlet filter class that calls Jersey's ApplicationHandler. Given a Jax RS Application object, this class * initializes a Jersey {@link ApplicationHandler} and calls its handle method. Requests are transformed diff --git a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServer.java b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServer.java index f4f227c4e..4be5d8565 100644 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServer.java +++ b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServer.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.spark.embeddedserver; import com.amazonaws.serverless.exceptions.ContainerInitializationException; diff --git a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerFactory.java b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerFactory.java index 3455170fa..9074e5ddf 100644 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerFactory.java +++ b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerFactory.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.spark.embeddedserver; import com.amazonaws.serverless.proxy.internal.testutils.Timer; 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 index edc76148d..2f06e73e6 100644 --- 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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; 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 index d975bd20e..9857c9620 100644 --- 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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 org.springframework.boot.context.embedded.WebApplicationContextServletContextAwareProcessor; 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 index 8af0efbca..689b6de28 100644 --- 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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; 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 index 4e9a8836c..bd567ed7d 100644 --- 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.embedded; import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; 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 index 28f36bae2..b9894834c 100644 --- 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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; diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java index 7fa366dd9..9b2c04948 100644 --- a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java +++ b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.embedded; import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; 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 index b47b081c7..ad228f7d4 100644 --- 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 @@ -1,6 +1,17 @@ +/* + * Copyright 2020 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.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; 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 ab08f5dd8..3a536c9b2 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 @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.struts2; import com.amazonaws.serverless.exceptions.ContainerInitializationException; diff --git a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaHandler.java b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaHandler.java index 1e63daf99..26a01bd90 100644 --- a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaHandler.java +++ b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaHandler.java @@ -1,3 +1,15 @@ +/* + * Copyright 2020 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.struts2; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; From 37c8dac5492115f9246b81bf68eb1a2eaad9bcca Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 8 Apr 2020 09:45:19 -0700 Subject: [PATCH 40/41] fix: Set default value for setDisableException mapper in config to false --- .../com/amazonaws/serverless/proxy/model/ContainerConfig.java | 1 + 1 file changed, 1 insertion(+) 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 5685dbb0c..16ba26f98 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 @@ -39,6 +39,7 @@ public static ContainerConfig defaultConfig() { configuration.addBinaryContentTypes("application/octet-stream", "image/jpeg", "image/png", "image/gif"); configuration.setDefaultContentCharset(DEFAULT_CONTENT_CHARSET); configuration.setInitializationTimeout(MAX_INIT_TIMEOUT_MS); + configuration.setDisableExceptionMapper(false); return configuration; } From 67a89520374b882685714496539a49bf7d8b5668 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 8 Apr 2020 09:49:02 -0700 Subject: [PATCH 41/41] fix: Updated default initialization timeout to 20 seconds --- .../com/amazonaws/serverless/proxy/model/ContainerConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 16ba26f98..5edeeebbe 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 @@ -26,7 +26,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; + private static final int MAX_INIT_TIMEOUT_MS = 20_000; public static ContainerConfig defaultConfig() { ContainerConfig configuration = new ContainerConfig();