diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..acc835c6f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,62 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: maven + directories: + - "**/*" + groups: + jersey: + patterns: + - "org.glassfish.jersey.*:*" + spring: + patterns: + - "org.springframework:*" + slf4j: + patterns: + - "org.slf4j:*" + jackson: + patterns: + - "com.fasterxml.jackson.*:*" + log4j: + patterns: + - "org.apache.logging.log4j:*" + junit: + patterns: + - "org.junit:*" + maven-install-plugin: + patterns: + - "org.apache.maven.plugins:maven-install-plugin" + httpclient: + patterns: + - "org.apache.httpcomponents.client5:*" + schedule: + interval: "weekly" + open-pull-requests-limit: 20 + - package-ecosystem: gradle + directories: + - "**/*" + groups: + jersey: + patterns: + - "org.glassfish.jersey.*:*" + spring: + patterns: + - "org.springframework:*" + slf4j: + patterns: + - "org.slf4j:*" + log4j: + patterns: + - "org.apache.logging.log4j:*" + jackson: + patterns: + - "com.fasterxml.jackson.*:*" + httpclient: + patterns: + - "org.apache.httpcomponents.client5:*" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 0047dd19e..08fe294d4 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -3,7 +3,13 @@ on: push: pull_request: branches: - - master + - main + - 2.0.x + - 1.x + workflow_dispatch: + +permissions: + contents: read jobs: build_core: @@ -11,6 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'corretto' + java-version: 17 - name: Build latest run: mvn -q clean install working-directory: ./aws-serverless-java-container-core @@ -20,65 +31,70 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Build latest - run: ./gha_build.sh jersey true true - - name: Set up JDK 8 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'corretto' - java-version: 8 - - 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 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 + java-version: 17 - name: Build latest - run: ./gha_build.sh spark true true + run: ./gha_build.sh jersey true true +# - name: Set up JDK 8 +# uses: actions/setup-java@v3 +# with: +# distribution: 'corretto' +# java-version: 8 +# - 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_spring: - name: Build and test Spring & SpringBoot + name: Build and test Spring runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'corretto' + java-version: 17 - name: Build latest - # 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 5.0 - run: ./gha_build.sh spring false false -Dspring.version=5.0.20.RELEASE -Dspring-security.version=5.0.19.RELEASE -Ddependency-check.skip=true - - name: Build Spring 5.1 - run: ./gha_build.sh spring false false -Dspring.version=5.1.20.RELEASE -Dspring-security.version=5.1.13.RELEASE -Ddependency-check.skip=true - - name: Build Spring 5.2 - run: ./gha_build.sh spring false false -Dspring.version=5.2.21.RELEASE -Dspring-security.version=5.2.15.RELEASE -Ddependency-check.skip=true + run: ./gha_build.sh spring true true +# - name: Build with Spring 6.0.x +# run: ./gha_build.sh spring false false -Dspring.version=6.0.16 -Dspring-security.version=6.1.10 -Ddependency-check.skip=true - build_springboot2: - name: Build and test SpringBoot 2 + build_springboot3: + name: Build and test SpringBoot 3 runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'corretto' + java-version: 17 - name: Build latest - run: ./gha_build.sh springboot2 true true - # https://github.com/spring-projects/spring-boot/wiki/Supported-Versions - - name: Build Spring Boot 2.2 - run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.2.13.RELEASE -Dspring.version=5.2.15.RELEASE -Dspringsecurity.version=5.2.8.RELEASE -Ddependency-check.skip=true - - name: Build Spring Boot 2.3 - run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.3.12.RELEASE -Dspring.version=5.2.15.RELEASE -Dspringsecurity.version=5.3.9.RELEASE -Ddependency-check.skip=true - - name: Build Spring Boot 2.4 - run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.4.13 -Dspring.version=5.3.13 -Dspringsecurity.version=5.4.9 -Ddependency-check.skip=true - - name: Build Spring Boot 2.5 - run: ./gha_build.sh springboot2 false false -Dspringboot.version=2.5.14 -Dspring.version=5.3.20 -Dspringsecurity.version=5.5.8 -Ddependency-check.skip=true + run: ./gha_build.sh springboot3 true true + # Build with additional supported versions https://spring.io/projects/spring-boot#support + - name: Build with Spring Boot 3.1.x + run: ./gha_build.sh springboot3 false false -Dspringboot.version=3.1.12 -Dspring.version=6.0.21 -Dspringsecurity.version=6.1.9 -Ddependency-check.skip=true + - name: Build with Spring Boot 3.2.x + run: ./gha_build.sh springboot3 false false -Dspringboot.version=3.2.7 -Dspring.version=6.1.10 -Dspringsecurity.version=6.2.5 -Ddependency-check.skip=true + - name: Build with Spring Boot 3.3.x + run: ./gha_build.sh springboot3 false false -Dspringboot.version=3.3.6 -Dspring.version=6.1.15 -Dspringsecurity.version=6.3.5 -Ddependency-check.skip=true - build_struts2: - name: Build and test Struts - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Build latest - run: ./gha_build.sh struts true true +# temporarily disabled as Struts is not released at the moment +# build_struts2: +# name: Build and test Struts +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v3 +# - name: Set up JDK 17 +# uses: actions/setup-java@v3 +# with: +# distribution: 'corretto' +# java-version: 17 +# - name: Build latest +# run: ./gha_build.sh struts true true \ No newline at end of file diff --git a/.github/workflows/owasp-dependency-check.yml b/.github/workflows/owasp-dependency-check.yml new file mode 100644 index 000000000..b7df2a770 --- /dev/null +++ b/.github/workflows/owasp-dependency-check.yml @@ -0,0 +1,21 @@ +name: OWASP dependency check +on: + schedule: + - cron: "10 10 * * 3" + +permissions: + contents: read + +jobs: + owasp-dependency-check: + name: Verify dependencies with OWASP checker + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'corretto' + java-version: 17 + - name: Build latest + run: mvn -q package org.owasp:dependency-check-maven:check \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 64ec8e2d5..9faff653b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,9 @@ on: description: "Version to use for further development" required: true default: "X.Y.Z-SNAPSHOT" +permissions: + contents: write + jobs: release: runs-on: ubuntu-latest @@ -23,7 +26,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'corretto' - java-version: 8 + java-version: 17 server-id: sonatype-nexus-staging server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD diff --git a/.gitignore b/.gitignore index 8f6b9d359..2aa1654c2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ *.jar *.war *.ear +*.project +*.classpath +*.settings # Idea project files .idea/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4d490d0fd..5d42e96a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ information to effectively respond to your bug report or contribution. We welcome you to use the GitHub issue tracker to report bugs or suggest features. -When filing an issue, please check [existing open](https://github.com/awslabs/aws-serverless-java-container/issues), or [recently closed](https://github.com/awslabs/aws-serverless-java-container/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already +When filing an issue, please check [existing open](https://github.com/aws/serverless-java-container/issues), or [recently closed](https://github.com/aws/serverless-java-container/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: * A reproducible test case or series of steps @@ -41,7 +41,7 @@ GitHub provides additional document on [forking a repository](https://help.githu ## Finding contributions to work on -Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-serverless-java-container/labels/help%20wanted) issues is a great place to start. +Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/serverless-java-container/labels/help%20wanted) issues is a great place to start. ## Code of Conduct @@ -56,6 +56,6 @@ If you discover a potential security issue in this project we ask that you notif ## Licensing -See the [LICENSE](https://github.com/awslabs/aws-serverless-java-container/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. +See the [LICENSE](https://github.com/aws/serverless-java-container/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. diff --git a/README.md b/README.md index cb98d749f..3f26e0c1b 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,25 @@ -# Serverless Java container [![Build Status](https://github.com/awslabs/aws-serverless-java-container/workflows/Continuous%20Integration/badge.svg)](https://github.com/awslabs/aws-serverless-java-container/actions) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.amazonaws.serverless/aws-serverless-java-container/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.amazonaws.serverless/aws-serverless-java-container) [![Help](http://img.shields.io/badge/help-gitter-E91E63.svg?style=flat-square)](https://gitter.im/awslabs/aws-serverless-java-container) +# Serverless Java container [![Build Status](https://github.com/aws/serverless-java-container/workflows/Continuous%20Integration/badge.svg)](https://github.com/aws/serverless-java-container/actions) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.amazonaws.serverless/aws-serverless-java-container/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.amazonaws.serverless/aws-serverless-java-container) [![Help](http://img.shields.io/badge/help-gitter-E91E63.svg?style=flat-square)](https://gitter.im/aws/serverless-java-container) The `aws-serverless-java-container` makes it easy to run Java applications written with frameworks such as [Spring](https://spring.io/), [Spring Boot](https://projects.spring.io/spring-boot/), [Apache Struts](http://struts.apache.org/), [Jersey](https://jersey.java.net/), or [Spark](http://sparkjava.com/) in [AWS Lambda](https://aws.amazon.com/lambda/). Serverless Java Container natively supports API Gateway's proxy integration models for requests and responses, you can create and inject custom models for methods that use custom mappings. -Follow the quick start guides in [our wiki](https://github.com/awslabs/aws-serverless-java-container/wiki) to integrate Serverless Java Container with your project: -* [Spring quick start](https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Spring) -* [Spring Boot 2 quick start](https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Spring-Boot2) -* [Apache Struts quick start](https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Struts) -* [Jersey quick start](https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Jersey) -* [Spark quick start](https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Spark) +Currently the following versions are maintained: -* Micronaut [documentation](https://guides.micronaut.io/micronaut-function-aws-lambda/guide/index.html) and [demo](https://github.com/awslabs/aws-serverless-java-container/tree/master/samples/micronaut/pet-store) (outdated! - needs to be updated to latest Micronaut version) +| Version | Branch | Java Enterprise support | Spring versions | JAX-RS/ Jersey version | Struts support | Spark support | +|---------|--------|-----------------------------|-----------------|------------------------|----------------|---------------| +| 1.x | [1.x](https://github.com/aws/serverless-java-container/tree/1.x) | Java EE (javax.*) | 5.x (Boot 2.x) | 2.x | :white_check_mark: | :white_check_mark: | +| 2.x | [main](https://github.com/aws/serverless-java-container/tree/main) | Jakarta EE 9-10 (jakarta.*) | 6.x (Boot 3.x) | 3.x | :x: | :x: | +| 3.x | | Jakarta EE 11 (jakarta.*) | 7.x (Boot 4.x) | 4.x | :x: | :x: | -Below is the most basic AWS Lambda handler example that launches a Spring application. You can also take a look at the [samples](https://github.com/awslabs/aws-serverless-java-container/tree/master/samples) in this repository, our main wiki page includes a [step-by-step guide](https://github.com/awslabs/aws-serverless-java-container/wiki#deploying-the-sample-applications) on how to deploy the various sample applications using Maven and [SAM](https://github.com/awslabs/serverless-application-model). +Follow the quick start guides in [our wiki](https://github.com/aws/serverless-java-container/wiki) to integrate Serverless Java Container with your project: +* [Spring quick start](https://github.com/aws/serverless-java-container/wiki/Quick-start---Spring) +* [Spring Boot 2 quick start](https://github.com/aws/serverless-java-container/wiki/Quick-start---Spring-Boot2) +* [Spring Boot 3 quick start](https://github.com/aws/serverless-java-container/wiki/Quick-start---Spring-Boot3) +* [Apache Struts quick start](https://github.com/aws/serverless-java-container/wiki/Quick-start---Struts) +* [Jersey quick start](https://github.com/aws/serverless-java-container/wiki/Quick-start---Jersey) +* [Spark quick start](https://github.com/aws/serverless-java-container/wiki/Quick-start---Spark) + +Below is the most basic AWS Lambda handler example that launches a Spring application. You can also take a look at the [samples](https://github.com/aws/serverless-java-container/tree/master/samples) in this repository, our main wiki page includes a [step-by-step guide](https://github.com/aws/serverless-java-container/wiki#deploying-the-sample-applications) on how to deploy the various sample applications using Maven and [SAM](https://github.com/awslabs/serverless-application-model). ```java public class StreamLambdaHandler implements RequestStreamHandler { @@ -38,6 +45,18 @@ public class StreamLambdaHandler implements RequestStreamHandler { ## Public Examples +### Blogs + +- [Re-platforming Java applications using the updated AWS Serverless Java Container](https://aws.amazon.com/blogs/compute/re-platforming-java-applications-using-the-updated-aws-serverless-java-container/) + +### Workshops + +- [Java on AWS Lambda](https://catalog.workshops.aws/java-on-aws-lambda) From Serverful to Serverless Java with AWS Lambda in 2 hours + ### Videos - [Spring on AWS Lambda](https://www.youtube.com/watch?v=A1rYiHTy9Lg&list=PLCOG9xkUD90IDm9tcY-5nMK6X6g8SD-Sz) YouTube Playlist from [@plantpowerjames](https://twitter.com/plantpowerjames) + +### Java samples with different frameworks + +- [Dagger, Micronaut, Quarkus, Spring Boot](https://github.com/aws-samples/serverless-java-frameworks-samples/) diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml index f5bae2f56..e9aa6bbec 100644 --- a/aws-serverless-java-container-core/pom.xml +++ b/aws-serverless-java-container-core/pom.xml @@ -6,107 +6,86 @@ AWS Serverless Java container support - Core Allows Java applications written for a servlet container to run in AWS Lambda https://aws.amazon.com/lambda - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT com.amazonaws.serverless aws-serverless-java-container - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT .. - 2.1 - 3.1.0 + 3.1.0 + 6.0.0 - com.amazonaws aws-lambda-java-core - 1.2.1 + 1.4.0 - - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api ${servlet.version} - - javax.ws.rs - javax.ws.rs-api + jakarta.ws.rs + jakarta.ws.rs-api ${jaxrs.version} - com.fasterxml.jackson.core jackson-databind ${jackson.version} - com.fasterxml.jackson.module jackson-module-afterburner ${jackson.version} - - - - commons-fileupload - commons-fileupload - 1.4 - - - - org.apache.httpcomponents - httpmime - 4.5.13 - compile + org.apache.commons + commons-fileupload2-jakarta-servlet6 + 2.0.0-M4 - - - org.apache.httpcomponents - httpclient - 4.5.13 - test - - - org.apache.httpcomponents - httpcore - 4.4.15 - compile - true - org.springframework.security spring-security-web - 5.7.4 + 6.5.1 test - - - - commons-io - commons-io - 2.11.0 - - - - + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + test-jar + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + org.jacoco jacoco-maven-plugin @@ -181,30 +160,7 @@ 7 false - - - - check - - - - - - - java9-plus - - [9,) - - - - com.sun.activation - jakarta.activation - 1.2.2 - - - - 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 d1e97909e..12e1590ab 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 @@ -13,7 +13,7 @@ package com.amazonaws.serverless.proxy; import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.InitializableLambdaContainerHandler; import com.amazonaws.services.lambda.runtime.Context; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; @@ -26,10 +26,10 @@ /** * An async implementation of the InitializationWrapper interface. This initializer calls the - * {@link LambdaContainerHandler#initialize()} in a separate thread. Then uses a latch to wait for the maximum Lambda + * {@link InitializableLambdaContainerHandler#initialize()} in a separate thread. Then uses a latch to wait for the maximum Lambda * initialization time of 10 seconds, if the initialize method takes longer than 10 seconds to return, the - * {@link #start(LambdaContainerHandler)} returns control to the caller and lets the initialization thread continue in - * the background. The {@link LambdaContainerHandler#proxy(Object, Context)} automatically waits for the latch of the + * {@link #start(InitializableLambdaContainerHandler)} returns control to the caller and lets the initialization thread continue in + * the background. The {@link com.amazonaws.serverless.proxy.internal.LambdaContainerHandler#proxy(Object, Context)} automatically waits for the latch of the * initializer to be released. * * The constructor of this class expects an epoch long. This is meant to be as close as possible to the time the Lambda @@ -38,6 +38,7 @@ * seconds has already been used up. */ public class AsyncInitializationWrapper extends InitializationWrapper { + private static final int DEFAULT_INIT_GRACE_TIME_MS = 150; private static final String INIT_GRACE_TIME_ENVIRONMENT_VARIABLE_NAME = "AWS_SERVERLESS_JAVA_CONTAINER_INIT_GRACE_TIME"; private static final int INIT_GRACE_TIME_MS = Integer.parseInt(System.getenv().getOrDefault( @@ -46,7 +47,8 @@ public class AsyncInitializationWrapper extends InitializationWrapper { private CountDownLatch initializationLatch; private final long actualStartTime; - private Logger log = LoggerFactory.getLogger(AsyncInitializationWrapper.class); + private final Logger log = LoggerFactory.getLogger(AsyncInitializationWrapper.class); + /** * Creates a new instance of the async initializer. @@ -66,7 +68,12 @@ public AsyncInitializationWrapper() { } @Override - public void start(LambdaContainerHandler handler) throws ContainerInitializationException { + public void start(InitializableLambdaContainerHandler handler) throws ContainerInitializationException { + if (InitializationTypeHelper.isAsyncInitializationDisabled()){ + log.info("Async init disabled due to \"{}\" initialization", InitializationTypeHelper.getInitializationType()); + super.start(handler); + return; + } initializationLatch = new CountDownLatch(1); AsyncInitializer initializer = new AsyncInitializer(initializationLatch, handler); Thread initThread = new Thread(initializer); @@ -96,15 +103,18 @@ public long getActualStartTimeMs() { @Override public CountDownLatch getInitializationLatch() { + if (InitializationTypeHelper.isAsyncInitializationDisabled()){ + return super.getInitializationLatch(); + } return initializationLatch; } private static class AsyncInitializer implements Runnable { - private LambdaContainerHandler handler; + private final InitializableLambdaContainerHandler handler; private CountDownLatch initLatch; - private Logger log = LoggerFactory.getLogger(AsyncInitializationWrapper.class); + private final Logger log = LoggerFactory.getLogger(AsyncInitializationWrapper.class); - AsyncInitializer(CountDownLatch latch, LambdaContainerHandler h) { + AsyncInitializer(CountDownLatch latch, InitializableLambdaContainerHandler h) { initLatch = latch; handler = h; } 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 05d4ed1a2..d4192141a 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 @@ -16,7 +16,7 @@ import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; import com.amazonaws.services.lambda.runtime.Context; -import javax.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.SecurityContext; public class AwsHttpApiV2SecurityContextWriter implements SecurityContextWriter { @Override diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java index cf2aae3e7..0ae00b4da 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java @@ -19,12 +19,13 @@ import com.amazonaws.serverless.proxy.model.Headers; import com.fasterxml.jackson.core.JsonProcessingException; +import jakarta.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.InternalServerErrorException; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.InternalServerErrorException; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; import java.io.IOException; import java.io.OutputStream; @@ -47,22 +48,22 @@ public class AwsProxyExceptionHandler // Constants //------------------------------------------------------------- - static final String INTERNAL_SERVER_ERROR = "Internal Server Error"; - static final String GATEWAY_TIMEOUT_ERROR = "Gateway timeout"; + static final String INTERNAL_SERVER_ERROR = Response.Status.INTERNAL_SERVER_ERROR.getReasonPhrase(); + static final String GATEWAY_TIMEOUT_ERROR = Response.Status.GATEWAY_TIMEOUT.getReasonPhrase(); //------------------------------------------------------------- // Variables - Private - Static //------------------------------------------------------------- - private static Headers headers = new Headers(); + protected static final Headers HEADERS = new Headers(); //------------------------------------------------------------- // Constructors //------------------------------------------------------------- static { - headers.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); + HEADERS.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); } @@ -79,9 +80,9 @@ public AwsProxyResponse handle(Throwable ex) { // output to go to the stderr. ex.printStackTrace(); if (ex instanceof InvalidRequestEventException || ex instanceof InternalServerErrorException) { - return new AwsProxyResponse(500, headers, getErrorJson(INTERNAL_SERVER_ERROR)); + return new AwsProxyResponse(500, HEADERS, getErrorJson(INTERNAL_SERVER_ERROR)); } else { - return new AwsProxyResponse(502, headers, getErrorJson(GATEWAY_TIMEOUT_ERROR)); + return new AwsProxyResponse(502, HEADERS, getErrorJson(GATEWAY_TIMEOUT_ERROR)); } } @@ -98,7 +99,7 @@ public void handle(Throwable ex, OutputStream stream) throws IOException { // Methods - Protected //------------------------------------------------------------- - String getErrorJson(String message) { + protected String getErrorJson(String message) { try { return LambdaContainerHandler.getObjectMapper().writeValueAsString(new ErrorModel(message)); diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriter.java index 4805e1193..8a58bc478 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriter.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriter.java @@ -16,7 +16,7 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.services.lambda.runtime.Context; -import javax.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.SecurityContext; /** * Default implementation of SecurityContextWriter. Creates a SecurityContext object based on an API Gateway diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/InitializationTypeHelper.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/InitializationTypeHelper.java new file mode 100644 index 000000000..c40c5ecca --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/InitializationTypeHelper.java @@ -0,0 +1,33 @@ +/* + * Copyright 2024 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; + +/** + * Utility class that helps determine the initialization type + */ +public final class InitializationTypeHelper { + + private static final String INITIALIZATION_TYPE_ENVIRONMENT_VARIABLE_NAME = "AWS_LAMBDA_INITIALIZATION_TYPE"; + private static final String INITIALIZATION_TYPE_ON_DEMAND = "on-demand"; + private static final String INITIALIZATION_TYPE = System.getenv().getOrDefault(INITIALIZATION_TYPE_ENVIRONMENT_VARIABLE_NAME, + INITIALIZATION_TYPE_ON_DEMAND); + private static final boolean ASYNC_INIT_DISABLED = !INITIALIZATION_TYPE.equals(INITIALIZATION_TYPE_ON_DEMAND); + + public static boolean isAsyncInitializationDisabled() { + return ASYNC_INIT_DISABLED; + } + + public static String getInitializationType() { + return INITIALIZATION_TYPE; + } +} 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 f7c96f8f4..a261b6cc8 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 @@ -13,13 +13,13 @@ package com.amazonaws.serverless.proxy; import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.InitializableLambdaContainerHandler; import java.util.concurrent.CountDownLatch; /** - * This class is in charge of initializing a {@link LambdaContainerHandler}. - * In most cases, this means calling the {@link LambdaContainerHandler#initialize()} method. Some implementations may + * This class is in charge of initializing a {@link InitializableLambdaContainerHandler}. + * In most cases, this means calling the {@link InitializableLambdaContainerHandler#initialize()} method. Some implementations may * require additional initialization steps, in this case implementations should provide their own * InitializationWrapper. This library includes an async implementation of this class * {@link AsyncInitializationWrapper} for frameworks that are likely to take longer than 10 seconds to start. @@ -31,7 +31,7 @@ public class InitializationWrapper { * @param handler The container handler to be initializer * @throws ContainerInitializationException If anything goes wrong during container initialization. */ - public void start(LambdaContainerHandler handler) throws ContainerInitializationException { + public void start(InitializableLambdaContainerHandler handler) throws ContainerInitializationException { handler.initialize(); } 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 dcd524f91..8bc75ca9c 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 @@ -12,7 +12,7 @@ */ package com.amazonaws.serverless.proxy; -import javax.ws.rs.core.SecurityContext; +import jakarta.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 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 e0203d76b..d8293d649 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 @@ -17,7 +17,7 @@ import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.services.lambda.runtime.Context; -import javax.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.SecurityContext; /** diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/SecurityContextWriter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/SecurityContextWriter.java index 32430d816..27a4c6c75 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/SecurityContextWriter.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/SecurityContextWriter.java @@ -15,7 +15,7 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.services.lambda.runtime.Context; -import javax.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.SecurityContext; /** * This object is used by the container implementation to generated a Jax-Rs SecurityContext object from the diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/HttpUtils.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/HttpUtils.java new file mode 100644 index 000000000..f6eba4157 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/HttpUtils.java @@ -0,0 +1,68 @@ +package com.amazonaws.serverless.proxy.internal; + +import org.apache.commons.io.Charsets; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; + +public final class HttpUtils { + + static final String HEADER_KEY_VALUE_SEPARATOR = "="; + static final String HEADER_VALUE_SEPARATOR = ";"; + static final String ENCODING_VALUE_KEY = "charset"; + + + static public Charset parseCharacterEncoding(String contentTypeHeader,Charset defaultCharset) { + // we only look at content-type because content-encoding should only be used for + // "binary" requests such as gzip/deflate. + if (contentTypeHeader == null) { + return defaultCharset; + } + + String[] contentTypeValues = contentTypeHeader.split(HEADER_VALUE_SEPARATOR); + if (contentTypeValues.length <= 1) { + return defaultCharset; + } + + for (String contentTypeValue : contentTypeValues) { + if (contentTypeValue.trim().startsWith(ENCODING_VALUE_KEY)) { + String[] encodingValues = contentTypeValue.split(HEADER_KEY_VALUE_SEPARATOR); + if (encodingValues.length <= 1) { + return defaultCharset; + } + try { + return Charsets.toCharset(encodingValues[1]); + } catch (UnsupportedCharsetException ex) { + return defaultCharset; + } + } + } + return defaultCharset; + } + + + static public String appendCharacterEncoding(String currentContentType, String newEncoding) { + if (currentContentType == null || currentContentType.trim().isEmpty()) { + 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; + } + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/InitializableLambdaContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/InitializableLambdaContainerHandler.java new file mode 100644 index 000000000..daf4a69d9 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/InitializableLambdaContainerHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024 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 com.amazonaws.serverless.exceptions.ContainerInitializationException; + +/** + * Interface to define initialization/ cold-start related methods. + * See also the documentation for + * + * AWS Lambda Execution Environments. + */ +public interface InitializableLambdaContainerHandler { + + /** + * This method is called on the first (cold) invocation + * + * @throws ContainerInitializationException in case initialization fails + */ + void initialize() throws ContainerInitializationException; +} 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 e29b1072c..ff978456b 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 @@ -28,7 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.SecurityContext; import java.io.IOException; import java.io.InputStream; @@ -45,7 +45,8 @@ * @param The request type for the wrapped Java container * @param The response or response writer type for the wrapped Java container */ -public abstract class LambdaContainerHandler { +public abstract class LambdaContainerHandler + implements InitializableLambdaContainerHandler { //------------------------------------------------------------- // Constants @@ -163,7 +164,7 @@ public void setInitializationWrapper(InitializationWrapper wrapper) { /** * Configures the library to strip a base path from incoming requests before passing them on to the wrapped - * framework. This was added in response to issue #34 (https://github.com/awslabs/aws-serverless-java-container/issues/34). + * framework. This was added in response to issue #34 (https://github.com/aws/serverless-java-container/issues/34). * When creating a base path mapping for custom domain names in API Gateway we want to be able to strip the base path * from the request - the underlying service may not recognize this path. * @param basePath The base path to be stripped from the request 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 d553d17a7..3f52f8950 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 @@ -12,6 +12,8 @@ */ package com.amazonaws.serverless.proxy.internal; +import com.amazonaws.serverless.proxy.model.AlbContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; import com.amazonaws.serverless.proxy.model.ContainerConfig; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; @@ -21,6 +23,7 @@ import java.io.IOException; import java.util.HashSet; import java.util.Locale; +import java.util.Objects; import java.util.Set; /** @@ -60,11 +63,15 @@ public static boolean isValidScheme(String scheme) { return SCHEMES.contains(scheme); } - public static boolean isValidHost(String host, String apiId, String region) { + public static boolean isValidHost(String host, String apiId, AlbContext elb, String region) { if (host == null) { return false; } - if (host.endsWith(".amazonaws.com")) { + if (!Objects.isNull(elb)) { + String albhost = new StringBuilder().append(region) + .append(".elb.amazonaws.com").toString(); + return host.endsWith(albhost) || LambdaContainerHandler.getContainerConfig().getCustomDomainNames().contains(host); + } else if (host.endsWith(".amazonaws.com")) { String defaultHost = new StringBuilder().append(apiId) .append(".execute-api.") .append(region) 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 6060fb2e2..f0e1f963d 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 @@ -21,8 +21,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.SecurityContext; import java.nio.charset.StandardCharsets; import java.security.Principal; import java.util.Base64; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java index 025dceacc..7c128cabe 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java @@ -16,13 +16,10 @@ import com.amazonaws.serverless.proxy.model.CognitoAuthorizerClaims; import com.amazonaws.services.lambda.runtime.Context; -import javax.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.SecurityContext; import java.security.Principal; -import static com.amazonaws.serverless.proxy.model.AwsProxyRequest.RequestSource.API_GATEWAY; - - /** * default implementation of the SecurityContext object. This class supports 3 API Gateway's authorization methods: * AWS_IAM, CUSTOM_AUTHORIZER, and COGNITO_USER_POOL (oidc). The Principal returned by the object depends on the authorization 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 715e6f8d9..e0c7a7357 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 @@ -13,15 +13,14 @@ 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; import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequestContext; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.SecurityContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.core.SecurityContext; import java.time.*; import java.time.format.DateTimeFormatter; 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 251624541..d64af8966 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 @@ -16,9 +16,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -32,23 +32,23 @@ public class AwsAsyncContext implements AsyncContext { private HttpServletRequest req; private HttpServletResponse res; - private AwsLambdaServletContainerHandler handler; private List listeners; private long timeout; private AtomicBoolean dispatched; private AtomicBoolean completed; + private AtomicBoolean dispatchStarted; private Logger log = LoggerFactory.getLogger(AwsAsyncContext.class); - public AwsAsyncContext(HttpServletRequest request, HttpServletResponse response, AwsLambdaServletContainerHandler servletHandler) { + public AwsAsyncContext(HttpServletRequest request, HttpServletResponse response) { log.debug("Initializing async context for request: " + SecurityUtils.crlf(request.getPathInfo()) + " - " + SecurityUtils.crlf(request.getMethod())); req = request; res = response; - handler = servletHandler; listeners = new ArrayList<>(); timeout = 3000; dispatched = new AtomicBoolean(false); completed = new AtomicBoolean(false); + dispatchStarted = new AtomicBoolean(false); } @Override @@ -68,16 +68,14 @@ public boolean hasOriginalRequestAndResponse() { @Override public void dispatch() { - try { - log.debug("Dispatching request"); - if (dispatched.get()) { - throw new IllegalStateException("Dispatching already started"); - } + log.debug("Dispatching request"); + + if (dispatched.get()) { + throw new IllegalStateException("Dispatching already started"); + } + if (dispatchStarted.getAndSet(true)) { 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); } } @@ -154,6 +152,10 @@ public boolean isCompleted() { return completed.get(); } + public boolean isDispatchStarted() { + return dispatchStarted.get(); + } + private void notifyListeners(NotificationType type, Throwable t) { listeners.forEach((h) -> { try { diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsCookieProcessor.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsCookieProcessor.java new file mode 100644 index 000000000..36ade344c --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsCookieProcessor.java @@ -0,0 +1,273 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import com.amazonaws.serverless.proxy.internal.SecurityUtils; +import jakarta.servlet.http.Cookie; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + * Implementation of the CookieProcessor interface that provides cookie parsing and generation functionality. + */ +public class AwsCookieProcessor implements CookieProcessor { + + // Cookie attribute constants + static final String COOKIE_COMMENT_ATTR = "Comment"; + static final String COOKIE_DOMAIN_ATTR = "Domain"; + static final String COOKIE_EXPIRES_ATTR = "Expires"; + static final String COOKIE_MAX_AGE_ATTR = "Max-Age"; + static final String COOKIE_PATH_ATTR = "Path"; + static final String COOKIE_SECURE_ATTR = "Secure"; + static final String COOKIE_HTTP_ONLY_ATTR = "HttpOnly"; + static final String COOKIE_SAME_SITE_ATTR = "SameSite"; + static final String COOKIE_PARTITIONED_ATTR = "Partitioned"; + static final String EMPTY_STRING = ""; + + // BitSet to store valid token characters as defined in RFC 2616 + static final BitSet tokenValid = createTokenValidSet(); + + // BitSet to validate domain characters + static final BitSet domainValid = createDomainValidSet(); + + static final DateTimeFormatter COOKIE_DATE_FORMATTER = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneId.of("GMT")); + + static final String ANCIENT_DATE = COOKIE_DATE_FORMATTER.format(Instant.ofEpochMilli(10000)); + + static BitSet createTokenValidSet() { + BitSet tokenSet = new BitSet(128); + for (char c = '0'; c <= '9'; c++) tokenSet.set(c); + for (char c = 'a'; c <= 'z'; c++) tokenSet.set(c); + for (char c = 'A'; c <= 'Z'; c++) tokenSet.set(c); + for (char c : "!#$%&'*+-.^_`|~".toCharArray()) tokenSet.set(c); + return tokenSet; + } + + static BitSet createDomainValidSet() { + BitSet domainValid = new BitSet(128); + for (char c = '0'; c <= '9'; c++) domainValid.set(c); + for (char c = 'a'; c <= 'z'; c++) domainValid.set(c); + for (char c = 'A'; c <= 'Z'; c++) domainValid.set(c); + domainValid.set('.'); + domainValid.set('-'); + return domainValid; + } + + private final Logger log = LoggerFactory.getLogger(AwsCookieProcessor.class); + + @Override + public Cookie[] parseCookieHeader(String cookieHeader) { + // Return an empty array if the input is null or empty after trimming + if (cookieHeader == null || cookieHeader.trim().isEmpty()) { + return new Cookie[0]; + } + + // Parse cookie header and convert to Cookie array + return Arrays.stream(cookieHeader.split("\\s*;\\s*")) + .map(this::parseCookiePair) + .filter(Objects::nonNull) // Filter out invalid pairs + .toArray(Cookie[]::new); + } + + /** + * Parse a single cookie pair (name=value). + * + * @param cookiePair The cookie pair string. + * @return A valid Cookie object or null if the pair is invalid. + */ + private Cookie parseCookiePair(String cookiePair) { + String[] kv = cookiePair.split("=", 2); + + if (kv.length != 2) { + log.warn("Ignoring invalid cookie: {}", cookiePair); + return null; // Skip malformed cookie pairs + } + + String cookieName = kv[0]; + String cookieValue = kv[1]; + + // Validate name and value + if (!isToken(cookieName)){ + log.warn("Ignoring cookie with invalid name: {}={}", cookieName, cookieValue); + return null; // Skip invalid cookie names + } + + if (!isValidCookieValue(cookieValue)) { + log.warn("Ignoring cookie with invalid value: {}={}", cookieName, cookieValue); + return null; // Skip invalid cookie values + } + + // Return a new Cookie object after security processing + return new Cookie(SecurityUtils.crlf(cookieName), SecurityUtils.crlf(cookieValue)); + } + + @Override + public String generateHeader(Cookie cookie) { + StringBuilder header = new StringBuilder(); + header.append(cookie.getName()).append('='); + + String value = cookie.getValue(); + if (value != null && value.length() > 0) { + validateCookieValue(value); + header.append(value); + } + + int maxAge = cookie.getMaxAge(); + if (maxAge == 0) { + appendAttribute(header, COOKIE_EXPIRES_ATTR, ANCIENT_DATE); + } else if (maxAge > 0){ + Instant expiresAt = Instant.now().plusSeconds(maxAge); + appendAttribute(header, COOKIE_EXPIRES_ATTR, COOKIE_DATE_FORMATTER.format(expiresAt)); + appendAttribute(header, COOKIE_MAX_AGE_ATTR, String.valueOf(maxAge)); + } + + String domain = cookie.getDomain(); + if (domain != null && !domain.isEmpty()) { + validateDomain(domain); + appendAttribute(header, COOKIE_DOMAIN_ATTR, domain); + } + + String path = cookie.getPath(); + if (path != null && !path.isEmpty()) { + validatePath(path); + appendAttribute(header, COOKIE_PATH_ATTR, path); + } + + if (cookie.getSecure()) { + appendAttributeWithoutValue(header, COOKIE_SECURE_ATTR); + } + + if (cookie.isHttpOnly()) { + appendAttributeWithoutValue(header, COOKIE_HTTP_ONLY_ATTR); + } + + String sameSite = cookie.getAttribute(COOKIE_SAME_SITE_ATTR); + if (sameSite != null) { + appendAttribute(header, COOKIE_SAME_SITE_ATTR, sameSite); + } + + String partitioned = cookie.getAttribute(COOKIE_PARTITIONED_ATTR); + if (EMPTY_STRING.equals(partitioned)) { + appendAttributeWithoutValue(header, COOKIE_PARTITIONED_ATTR); + } + + addAdditionalAttributes(cookie, header); + + return header.toString(); + } + + private void appendAttribute(StringBuilder header, String name, String value) { + header.append("; ").append(name); + if (!EMPTY_STRING.equals(value)) { + header.append('=').append(value); + } + } + + private void appendAttributeWithoutValue(StringBuilder header, String name) { + header.append("; ").append(name); + } + + private void addAdditionalAttributes(Cookie cookie, StringBuilder header) { + for (Map.Entry entry : cookie.getAttributes().entrySet()) { + switch (entry.getKey()) { + case COOKIE_COMMENT_ATTR: + case COOKIE_DOMAIN_ATTR: + case COOKIE_MAX_AGE_ATTR: + case COOKIE_PATH_ATTR: + case COOKIE_SECURE_ATTR: + case COOKIE_HTTP_ONLY_ATTR: + case COOKIE_SAME_SITE_ATTR: + case COOKIE_PARTITIONED_ATTR: + // Already handled attributes are ignored + break; + default: + validateAttribute(entry.getKey(), entry.getValue()); + appendAttribute(header, entry.getKey(), entry.getValue()); + break; + } + } + } + + private void validateCookieValue(String value) { + if (!isValidCookieValue(value)) { + throw new IllegalArgumentException("Invalid cookie value: " + value); + } + } + + private void validateDomain(String domain) { + if (!isValidDomain(domain)) { + throw new IllegalArgumentException("Invalid cookie domain: " + domain); + } + } + + private void validatePath(String path) { + for (char ch : path.toCharArray()) { + if (ch < 0x20 || ch > 0x7E || ch == ';') { + throw new IllegalArgumentException("Invalid cookie path: " + path); + } + } + } + + private void validateAttribute(String name, String value) { + if (!isToken(name)) { + throw new IllegalArgumentException("Invalid cookie attribute name: " + name); + } + + for (char ch : value.toCharArray()) { + if (ch < 0x20 || ch > 0x7E || ch == ';') { + throw new IllegalArgumentException("Invalid cookie attribute value: " + ch); + } + } + } + + private boolean isValidCookieValue(String value) { + int start = 0; + int end = value.length(); + boolean quoted = end > 1 && value.charAt(0) == '"' && value.charAt(end - 1) == '"'; + + char[] chars = value.toCharArray(); + for (int i = start; i < end; i++) { + if (quoted && (i == start || i == end - 1)) { + continue; + } + char c = chars[i]; + if (!isValidCookieChar(c)) return false; + } + return true; + } + + private boolean isValidDomain(String domain) { + if (domain.isEmpty()) { + return false; + } + int prev = -1; + for (char c : domain.toCharArray()) { + if (!domainValid.get(c) || isInvalidLabelStartOrEnd(prev, c)) { + return false; + } + prev = c; + } + return prev != '.' && prev != '-'; + } + + private boolean isInvalidLabelStartOrEnd(int prev, char current) { + return (prev == '.' || prev == -1) && (current == '.' || current == '-') || + (prev == '-' && current == '.'); + } + + private boolean isToken(String s) { + if (s.isEmpty()) return false; + for (char c : s.toCharArray()) { + if (!tokenValid.get(c)) { + return false; + } + } + return true; + } + + private boolean isValidCookieChar(char c) { + return !(c < 0x21 || c > 0x7E || c == 0x22 || c == 0x2c || c == 0x3b || c == 0x5c); + } +} 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 bacedbaa4..c40740c96 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 @@ -18,8 +18,8 @@ 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; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.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"; 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 09a5e8846..f318b7277 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 @@ -12,6 +12,7 @@ */ package com.amazonaws.serverless.proxy.internal.servlet; +import com.amazonaws.serverless.proxy.internal.HttpUtils; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.internal.SecurityUtils; import com.amazonaws.serverless.proxy.model.ContainerConfig; @@ -23,15 +24,16 @@ 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 jakarta.servlet.*; +import jakarta.servlet.http.*; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.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.nio.charset.Charset; import java.security.Principal; import java.time.Instant; import java.time.ZonedDateTime; @@ -80,26 +82,14 @@ public Cookie[] getCookies() { if (headers == null || !headers.containsKey(HttpHeaders.COOKIE)) { rhc = new Cookie[0]; } else { - rhc = parseCookieHeaderValue(headers.getFirst(HttpHeaders.COOKIE)); + rhc = getCookieProcessor().parseCookieHeader(headers.getFirst(HttpHeaders.COOKIE)); } Cookie[] rc; if (request.getCookies() == null) { rc = new Cookie[0]; } else { - rc = request.getCookies().stream() - .map(c -> { - int i = c.indexOf('='); - if (i == -1) { - return null; - } else { - String k = SecurityUtils.crlf(c.substring(0, i)).trim(); - String v = SecurityUtils.crlf(c.substring(i+1)); - return new Cookie(k, v); - } - }) - .filter(c -> c != null) - .toArray(Cookie[]::new); + rc = getCookieProcessor().parseCookieHeader(String.join("; ", request.getCookies())); } return Stream.concat(Arrays.stream(rhc), Arrays.stream(rc)).toArray(Cookie[]::new); @@ -234,16 +224,6 @@ 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(); @@ -254,7 +234,8 @@ public String getCharacterEncoding() { if (headers == null) { return config.getDefaultContentCharset(); } - return parseCharacterEncoding(headers.getFirst(HttpHeaders.CONTENT_TYPE)); + Charset charset = HttpUtils.parseCharacterEncoding(headers.getFirst(HttpHeaders.CONTENT_TYPE),null); + return charset != null ? charset.name() : null; } @Override @@ -264,7 +245,7 @@ public void setCharacterEncoding(String s) throws UnsupportedEncodingException { return; } String currentContentType = headers.getFirst(HttpHeaders.CONTENT_TYPE); - headers.putSingle(HttpHeaders.CONTENT_TYPE, appendCharacterEncoding(currentContentType, s)); + headers.putSingle(HttpHeaders.CONTENT_TYPE, HttpUtils.appendCharacterEncoding(currentContentType, s)); } @Override @@ -320,8 +301,16 @@ 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(queryString, s, config.isQueryStringCaseSensitive()))); + List values = getQueryParamValuesAsList(queryString, s, config.isQueryStringCaseSensitive()); + + // copy list so we don't modifying the underlying multi-value query params + if (values != null) { + values = new ArrayList<>(values); + } else { + values = new ArrayList<>(); + } + values.addAll(Arrays.asList(getFormBodyParameterCaseInsensitive(s))); if (values.size() == 0) { @@ -357,7 +346,7 @@ public String getServerName() { if (headers != null && headers.containsKey(HOST_HEADER_NAME)) { String hostHeader = headers.getFirst(HOST_HEADER_NAME); - if (SecurityUtils.isValidHost(hostHeader, request.getRequestContext().getApiId(), region)) { + if (SecurityUtils.isValidHost(hostHeader, request.getRequestContext().getApiId(), request.getRequestContext().getElb(), region)) { return hostHeader; } } @@ -408,31 +397,13 @@ public String getRemoteHost() { @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()); + List locales = parseAcceptLanguageHeader(headers.getFirst(HttpHeaders.ACCEPT_LANGUAGE)); + return locales.size() == 0 ? Locale.getDefault() : locales.get(0); } @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())); - } - } - + List locales = parseAcceptLanguageHeader(headers.getFirst(HttpHeaders.ACCEPT_LANGUAGE)); return Collections.enumeration(locales); } @@ -446,12 +417,6 @@ 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; @@ -475,7 +440,7 @@ public boolean isAsyncStarted() { @Override public AsyncContext startAsync() throws IllegalStateException { - asyncContext = new AwsAsyncContext(this, response, containerHandler); + asyncContext = new AwsAsyncContext(this, response); setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ASYNC); log.debug("Starting async context for request: " + SecurityUtils.crlf(request.getRequestContext().getRequestId())); return asyncContext; @@ -483,7 +448,7 @@ public AsyncContext startAsync() throws IllegalStateException { @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { - asyncContext = new AwsAsyncContext((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, containerHandler); + asyncContext = new AwsAsyncContext((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ASYNC); log.debug("Starting async context for request: " + SecurityUtils.crlf(request.getRequestContext().getRequestId())); return asyncContext; @@ -498,6 +463,21 @@ public AsyncContext getAsyncContext() { return asyncContext; } + @Override + public String getRequestId() { + return request.getRequestContext().getRequestId(); + } + + @Override + public String getProtocolRequestId() { + return ""; + } + + @Override + public ServletConnection getServletConnection() { + return null; + } + private MultiValuedTreeMap parseRawQueryString(String qs) { if (qs == null || "".equals(qs.trim())) { return new MultiValuedTreeMap<>(); @@ -514,10 +494,10 @@ private MultiValuedTreeMap parseRawQueryString(String qs) { String[] kv = value.split(QUERY_STRING_KEY_VALUE_SEPARATOR); String key = URLDecoder.decode(kv[0], LambdaContainerHandler.getContainerConfig().getUriEncoding()); - String val = kv.length == 2 ? kv[1] : ""; + String val = kv.length == 2 ? AwsHttpServletRequest.decodeValueIfEncoded(kv[1]) : ""; qsMap.add(key, val); } catch (UnsupportedEncodingException e) { - log.error("Unsupported encoding in query string key: " + SecurityUtils.crlf(value), e); + log.error("Unsupported encoding in query string key-value pair: " + SecurityUtils.crlf(value), e); } } return qsMap; 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 61d97bfef..0029ee2d4 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 @@ -16,32 +16,37 @@ 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.*; +import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; +import com.amazonaws.serverless.proxy.model.ContainerConfig; +import com.amazonaws.serverless.proxy.model.Headers; +import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; 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.fileupload2.core.DiskFileItem; +import org.apache.commons.fileupload2.core.FileItem; +import org.apache.commons.fileupload2.core.FileUploadException; +import org.apache.commons.fileupload2.core.DiskFileItemFactory; +import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload; 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; -import javax.servlet.*; -import javax.servlet.http.*; -import javax.ws.rs.core.MediaType; +import jakarta.servlet.*; +import jakarta.servlet.http.*; +import jakarta.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.nio.charset.Charset; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** @@ -71,6 +76,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { static final String PROTOCOL_HEADER_NAME = "X-Forwarded-Proto"; static final String HOST_HEADER_NAME = "Host"; static final String PORT_HEADER_NAME = "X-Forwarded-Port"; + static final String CLIENT_IP_HEADER = "X-Forwarded-For"; //------------------------------------------------------------- @@ -82,8 +88,9 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { private ServletContext servletContext; private AwsHttpSession session; private String queryString; - private Map multipartFormParameters; + private Map> multipartFormParameters; private Map> urlEncodedFormParameters; + private CookieProcessor cookieProcessor; protected AwsHttpServletResponse response; protected AwsLambdaServletContainerHandler containerHandler; @@ -142,10 +149,13 @@ public HttpSession getSession(boolean b) { } + /** + * as per Servlet spec this method creates a session if none exists + * @return exisiting or new http session + */ @Override public HttpSession getSession() { - log.debug("Trying to access session. Lambda functions are stateless and should not rely on the session"); - return this.session; + return getSession(true); } @@ -176,15 +186,6 @@ public boolean isRequestedSessionIdFromURL() { return false; } - - @Override - @Deprecated - public boolean isRequestedSessionIdFromUrl() { - log.debug("Trying to access session. Lambda functions are stateless and should not rely on the session"); - return false; - } - - //------------------------------------------------------------- // Implementation - ServletRequest //------------------------------------------------------------- @@ -295,12 +296,7 @@ public void setServletContext(ServletContext context) { * @return An array of Cookie objects from the header */ protected Cookie[] parseCookieHeaderValue(String headerValue) { - List parsedHeaders = this.parseHeaderValue(headerValue, ";", ","); - - return parsedHeaders.stream() - .filter(e -> e.getKey() != null) - .map(e -> new Cookie(SecurityUtils.crlf(e.getKey()), SecurityUtils.crlf(e.getValue()))) - .toArray(Cookie[]::new); + return getCookieProcessor().parseCookieHeader(headerValue); } @@ -314,7 +310,7 @@ protected Cookie[] parseCookieHeaderValue(String headerValue) { */ protected String generateQueryString(MultiValuedTreeMap parameters, boolean encode, String encodeCharset) throws ServletException { - if (parameters == null || parameters.size() == 0) { + if (parameters == null || parameters.isEmpty()) { return null; } if (queryString != null) { @@ -373,53 +369,9 @@ protected StringBuffer generateRequestURL(String 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) { @@ -431,13 +383,13 @@ protected ServletInputStream bodyStringToInputStream(String body, boolean isBase } else { String encoding = getCharacterEncoding(); if (encoding == null) { - encoding = StandardCharsets.ISO_8859_1.name(); + encoding = Charset.defaultCharset().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()); + bodyBytes = body.getBytes(Charset.defaultCharset()); } } ByteArrayInputStream requestBodyStream = new ByteArrayInputStream(bodyBytes); @@ -488,7 +440,7 @@ protected Map> getFormUrlEncodedParametersMap() { Timer.start("SERVLET_REQUEST_GET_FORM_PARAMS"); String rawBodyContent = null; try { - rawBodyContent = IOUtils.toString(getInputStream()); + rawBodyContent = IOUtils.toString(getInputStream(), getCharacterEncoding()); } catch (IOException e) { throw new RuntimeException(e); } @@ -512,23 +464,52 @@ protected Map> getFormUrlEncodedParametersMap() { return urlEncodedFormParameters; } + protected CookieProcessor getCookieProcessor(){ + if (cookieProcessor == null) { + cookieProcessor = new AwsCookieProcessor(); + } + return cookieProcessor; + } + + @Override + public Collection getParts() + throws IOException, ServletException { + List partList = + getMultipartFormParametersMap().values().stream() + .flatMap(List::stream) + .collect(Collectors.toList()); + return partList; + } + + @Override + public Part getPart(String s) + throws IOException, ServletException { + // In case there's multiple files with the same fieldName, we return the first one in the list + List values = getMultipartFormParametersMap().get(s); + if (Objects.isNull(values)) { + return null; + } + return getMultipartFormParametersMap().get(s).get(0); + } + @SuppressFBWarnings({"FILE_UPLOAD_FILENAME", "WEAK_FILENAMEUTILS"}) - protected Map getMultipartFormParametersMap() { + protected Map> getMultipartFormParametersMap() throws IOException { if (multipartFormParameters != null) { return multipartFormParameters; } - if (!ServletFileUpload.isMultipartContent(this)) { // isMultipartContent also checks the content type + if (!JakartaServletFileUpload.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()); + JakartaServletFileUpload upload = + new JakartaServletFileUpload<>(DiskFileItemFactory.builder().get()); try { - List items = upload.parseRequest(this); - for (FileItem item : items) { + 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()); @@ -538,8 +519,7 @@ protected Map getMultipartFormParametersMap() { item.getHeaders().getHeaderNames().forEachRemaining(h -> { newPart.addHeader(h, item.getHeaders().getHeader(h)); }); - - multipartFormParameters.put(item.getFieldName(), newPart); + addPart(multipartFormParameters, item.getFieldName(), newPart); } } catch (FileUploadException e) { Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS"); @@ -548,42 +528,90 @@ protected Map getMultipartFormParametersMap() { Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS"); return multipartFormParameters; } + private void addPart(Map> params, String fieldName, Part newPart) { + List partList = params.get(fieldName); + if (Objects.isNull(partList)) { + partList = new ArrayList<>(); + params.put(fieldName, partList); + } + partList.add(newPart); + } protected String[] getQueryParamValues(MultiValuedTreeMap qs, String key, boolean isCaseSensitive) { + List value = getQueryParamValuesAsList(qs, key, isCaseSensitive); + if (value == null) { + return null; + } + return value.toArray(new String[0]); + } + + public static List getQueryParamValuesAsList(MultiValuedTreeMap qs, String key, boolean isCaseSensitive) { if (qs != null) { if (isCaseSensitive) { - return qs.get(key).toArray(new String[0]); + return qs.get(key); } for (String k : qs.keySet()) { if (k.toLowerCase(Locale.getDefault()).equals(key.toLowerCase(Locale.getDefault()))) { - return qs.get(k).toArray(new String[0]); + return qs.get(k); } } } - return new String[0]; + return Collections.emptyList(); } 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 generateParameterMap(qs, config, false); + } + + protected Map generateParameterMap(MultiValuedTreeMap qs, ContainerConfig config, boolean decodeQueryParams) { + Map output; + + Map> formEncodedParams = getFormUrlEncodedParametersMap(); + + if (qs == null) { + // Just transform the List values to String[] + return formEncodedParams.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, (e) -> e.getValue().toArray(new String[0]))); + } + + // decode all keys and values in map + final MultiValuedTreeMap decodedQs = new MultiValuedTreeMap(); + if (decodeQueryParams) { + for (Map.Entry> entry : qs.entrySet()) { + String k = decodeValueIfEncoded(entry.getKey()); + List v = getQueryParamValuesAsList(qs, entry.getKey(), false).stream() + .map(AwsHttpServletRequest::decodeValueIfEncoded) + .collect(Collectors.toList()); + // addAll in case map has 2 keys that are identical once decoded + decodedQs.addAll(k, v); + } + } else { + decodedQs.putAll(qs); } + + Map> queryStringParams; + if (config.isQueryStringCaseSensitive()) { + queryStringParams = decodedQs; + } else { + // If it's case insensitive, we check the entire map on every parameter + queryStringParams = decodedQs.entrySet().stream().collect( + Collectors.toMap( + Map.Entry::getKey, + e -> getQueryParamValuesAsList(decodedQs, e.getKey(), false) + )); + } + + // Merge formEncodedParams and queryStringParams Maps + output = Stream.of(formEncodedParams, queryStringParams).flatMap(m -> m.entrySet().stream()) + .collect( + Collectors.toMap( + Map.Entry::getKey, + e -> e.getValue().toArray(new String[0]), + // If a parameter is in both Maps, we merge the list of values (and ultimately transform to String[]) + (formParam, queryParam) -> Stream.of(formParam, queryParam).flatMap(Stream::of).toArray(String[]::new) + )); return output; } @@ -635,11 +663,10 @@ protected List parseHeaderValue(String headerValue, String valueSep return values; } - for (String v : headerValue.split(valueSeparator)) { - String curValue = v; + for (String curValue : headerValue.split(valueSeparator)) { float curPreference = 1.0f; HeaderValue newValue = new HeaderValue(); - newValue.setRawValue(v); + newValue.setRawValue(curValue); for (String q : curValue.split(qualifierSeparator)) { @@ -655,7 +682,7 @@ protected List parseHeaderValue(String headerValue, String valueSep // if the length of the value is 0 we assume that we are looking at a // base64 encoded value with padding so we just set the value. This is because // we assume that empty values in a key/value pair will contain at least a white space - if (kv[1].length() == 0) { + if (kv[1].isEmpty()) { val = q.trim(); } // this was a base64 string with an additional = for padding, set the value only @@ -696,6 +723,40 @@ protected List parseHeaderValue(String headerValue, String valueSep return values; } + protected List parseAcceptLanguageHeader(String headerValue) { + // Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5 + List values = this.parseHeaderValue( + headerValue, ",", ";" + ); + + List locales = new ArrayList<>(); + if (values.isEmpty()) { + locales.add(Locale.getDefault()); + } else { + for (HeaderValue locale : values) { + locales.add(parseLanguageTag(locale.getValue())); + } + } + + return locales; + } + + protected Locale parseLanguageTag(String languageTag) { + languageTag = languageTag.trim(); + String language; + String country = ""; + + int indexDash = languageTag.indexOf('-'); + if (indexDash > -1) { + country = languageTag.substring(indexDash + 1).trim(); + language = languageTag.substring(0, indexDash).trim(); + } else { + language = languageTag; + } + + return new Locale(language, country); + } + static String decodeRequestPath(String requestPath, ContainerConfig config) { try { return URLDecoder.decode(requestPath, config.getUriEncoding()); @@ -726,7 +787,7 @@ static String cleanUri(String uri) { return finalUri; } - static String decodeValueIfEncoded(String value) { + public static String decodeValueIfEncoded(String value) { if (value == null) { return null; } 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 e151c03da..58d7282ba 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 @@ -15,8 +15,8 @@ import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import javax.servlet.*; -import javax.servlet.http.*; +import jakarta.servlet.*; +import jakarta.servlet.http.*; import java.io.BufferedReader; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -177,11 +177,6 @@ public boolean isRequestedSessionIdFromURL() { return originalRequest.isRequestedSessionIdFromURL(); } - @Override - public boolean isRequestedSessionIdFromUrl() { - return originalRequest.isRequestedSessionIdFromUrl(); - } - @Override public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { return originalRequest.authenticate(httpServletResponse); @@ -343,11 +338,6 @@ public RequestDispatcher getRequestDispatcher(String s) { return originalRequest.getRequestDispatcher(s); } - @Override - public String getRealPath(String s) { - return originalRequest.getRealPath(s); - } - @Override public int getRemotePort() { return originalRequest.getRemotePort(); @@ -402,4 +392,19 @@ public AsyncContext getAsyncContext() { public DispatcherType getDispatcherType() { return originalRequest.getDispatcherType(); } + + @Override + public String getRequestId() { + return originalRequest.getRequestId(); + } + + @Override + public String getProtocolRequestId() { + return originalRequest.getProtocolRequestId(); + } + + @Override + public ServletConnection getServletConnection() { + return originalRequest.getServletConnection(); + } } 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 63a2dca80..f5395a0bf 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java @@ -21,14 +21,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.DispatcherType; -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.WriteListener; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; @@ -70,6 +70,7 @@ public class AwsHttpServletResponse private CountDownLatch writersCountDownLatch; private HttpServletRequest request; private boolean isCommitted = false; + private CookieProcessor cookieProcessor; private Logger log = LoggerFactory.getLogger(AwsHttpServletResponse.class); @@ -102,33 +103,7 @@ public void addCookie(Cookie cookie) { if (request != null && request.getDispatcherType() == DispatcherType.INCLUDE && isCommitted()) { throw new IllegalStateException("Cannot add Cookies for include request when response is committed"); } - String cookieData = cookie.getName() + "=" + cookie.getValue(); - if (cookie.getPath() != null) { - cookieData += "; Path=" + cookie.getPath(); - } - if (cookie.getSecure()) { - cookieData += "; Secure"; - } - if (cookie.isHttpOnly()) { - cookieData += "; HttpOnly"; - } - if (cookie.getDomain() != null && !"".equals(cookie.getDomain().trim())) { - cookieData += "; Domain=" + cookie.getDomain(); - } - - if (cookie.getMaxAge() > 0) { - cookieData += "; Max-Age=" + cookie.getMaxAge(); - - // we always set the timezone to GMT - TimeZone gmtTimeZone = TimeZone.getTimeZone(COOKIE_DEFAULT_TIME_ZONE); - Calendar currentTimestamp = Calendar.getInstance(gmtTimeZone); - currentTimestamp.add(Calendar.SECOND, cookie.getMaxAge()); - SimpleDateFormat cookieDateFormatter = new SimpleDateFormat(HEADER_DATE_PATTERN); - cookieDateFormatter.setTimeZone(gmtTimeZone); - cookieData += "; Expires=" + cookieDateFormatter.format(currentTimestamp.getTime()); - } - - setHeader(HttpHeaders.SET_COOKIE, cookieData, false); + setHeader(HttpHeaders.SET_COOKIE, getCookieProcessor().generateHeader(cookie), false); } @@ -152,24 +127,10 @@ public String encodeRedirectURL(String s) { } - @Override - @Deprecated - public String encodeUrl(String s) { - return this.encodeURL(s); - } - - - @Override - @Deprecated - public String encodeRedirectUrl(String s) { - return this.encodeRedirectURL(s); - } - - @Override public void sendError(int i, String s) throws IOException { request.setAttribute(AwsHttpServletRequest.DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ERROR); - setStatus(i, s); + setStatus(i); flushBuffer(); } @@ -213,7 +174,16 @@ public void addDateHeader(String s, long l) { @Override public void setHeader(String s, String s1) { if (!canSetHeader()) return; - setHeader(s, s1, true); + if (isContentTypeHeader(s)) { + setContentType(s1); + } else { + setHeader(s, s1, true); + } + } + + + private boolean isContentTypeHeader(String s) { + return s.toLowerCase(Locale.getDefault()).equals(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault())); } @@ -221,7 +191,7 @@ public void setHeader(String s, String s1) { public void addHeader(String s, String s1) { if (!canSetHeader()) return; // TODO: We should probably have a list of headers that we are not allowed to have multiple values for - if (s.toLowerCase(Locale.getDefault()).equals(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault()))) { + if (isContentTypeHeader(s)) { setContentType(s1); } else { setHeader(s, s1, false); @@ -249,16 +219,6 @@ public void setStatus(int i) { statusCode = i; } - - @Override - @Deprecated - public void setStatus(int i, String s) { - if (!canSetHeader()) return; - statusCode = i; - statusMessage = s; - } - - @Override public int getStatus() { return (statusCode <= 0?SC_OK:statusCode); @@ -337,6 +297,10 @@ public void write(int b) throws IOException { } } + @Override + public void flush() throws IOException { + flushBuffer(); + } @Override public void close() @@ -511,6 +475,12 @@ AwsProxyRequest getAwsProxyRequest() { return (AwsProxyRequest)request.getAttribute(API_GATEWAY_EVENT_PROPERTY); } + CookieProcessor getCookieProcessor(){ + if (cookieProcessor == null) { + cookieProcessor = new AwsCookieProcessor(); + } + return cookieProcessor; + } //------------------------------------------------------------- // Methods - Private @@ -526,9 +496,12 @@ private void setHeader(String key, String value, boolean overwrite) { values = new ArrayList<>(); } - values.add(encodedValue); - - headers.put(encodedKey, values); + if (value == null && overwrite) { + headers.remove(encodedKey); + } else if (value != null) { + values.add(encodedValue); + headers.put(encodedKey, values); + } } private boolean canSetHeader() { 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 8c3df7f86..2163320da 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 @@ -15,9 +15,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpSessionContext; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpSession; import java.time.Instant; import java.util.Collections; @@ -87,60 +86,30 @@ public int getMaxInactiveInterval() { return maxInactiveInterval; } - @Override - @Deprecated - public HttpSessionContext getSessionContext() { - throw new UnsupportedOperationException("Session context is deprecated and no longer supported"); - } - @Override public Object getAttribute(String name) { touch(); return attributes.get(name); } - @Override - @Deprecated - public Object getValue(String name) { - throw new UnsupportedOperationException("Session values are deprecated and not supported"); - } - @Override public Enumeration getAttributeNames() { touch(); return Collections.enumeration(attributes.keySet()); } - @Override - @Deprecated - public String[] getValueNames() { - throw new UnsupportedOperationException("Session values are deprecated and not supported"); - } - @Override public void setAttribute(String name, Object value) { touch(); attributes.put(name, value); } - @Override - @Deprecated - public void putValue(String name, Object value) { - throw new UnsupportedOperationException("Session values are deprecated and not supported"); - } - @Override public void removeAttribute(String name) { touch(); attributes.remove(name); } - @Override - @Deprecated - public void removeValue(String name) { - throw new UnsupportedOperationException("Session values are deprecated and not supported"); - } - @Override public void invalidate() { valid = false; 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 52631da54..7437449e1 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 @@ -19,18 +19,16 @@ import com.amazonaws.serverless.proxy.ResponseWriter; import com.amazonaws.serverless.proxy.SecurityContextWriter; -import com.amazonaws.services.lambda.runtime.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; import java.io.IOException; import java.util.*; -import java.util.stream.Collectors; /** @@ -154,13 +152,25 @@ protected void doFilter(HttpServletRequest request, HttpServletResponse response FilterChain chain = getFilterChain(request, servlet); chain.doFilter(request, response); - + if(requiresAsyncReDispatch(request)) { + chain = getFilterChain(request, servlet); + chain.doFilter(request, response); + } // 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(); } } + private boolean requiresAsyncReDispatch(HttpServletRequest request) { + if (request.isAsyncStarted()) { + AsyncContext asyncContext = request.getAsyncContext(); + return asyncContext instanceof AwsAsyncContext + && ((AwsAsyncContext) asyncContext).isDispatchStarted(); + } + return false; + } + @Override public void initialize() throws ContainerInitializationException { // we expect all servlets to be wrapped in an AwsServletRegistration 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 eecda99cb..9c4b7971b 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 @@ -13,43 +13,38 @@ package com.amazonaws.serverless.proxy.internal.servlet; +import com.amazonaws.serverless.proxy.internal.HttpUtils; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.internal.SecurityUtils; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.serverless.proxy.model.Headers; +import com.amazonaws.serverless.proxy.model.RequestSource; import com.amazonaws.services.lambda.runtime.Context; - import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import jakarta.servlet.*; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpUpgradeHandler; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.SecurityContext; 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.nio.charset.Charset; 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.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; - /** * Implementation of the HttpServletRequest interface that supports AwsProxyRequest object. * This object is initialized with an AwsProxyRequest event and a SecurityContext generated @@ -204,7 +199,7 @@ public String getQueryString() { return this.generateQueryString( request.getMultiValueQueryStringParameters(), // ALB does not automatically decode parameters, so we don't want to re-encode them - request.getRequestSource() != AwsProxyRequest.RequestSource.ALB, + request.getRequestSource() != RequestSource.ALB, config.getUriEncoding()); } catch (ServletException e) { log.error("Could not generate query string", e); @@ -264,21 +259,6 @@ public void logout() 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 { @@ -295,7 +275,8 @@ public String getCharacterEncoding() { if (request.getMultiValueHeaders() == null) { return config.getDefaultContentCharset(); } - return parseCharacterEncoding(request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + Charset charset = HttpUtils.parseCharacterEncoding(request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE),null); + return charset != null ? charset.name() : null; } @@ -306,12 +287,12 @@ public void setCharacterEncoding(String s) request.setMultiValueHeaders(new Headers()); } String currentContentType = request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE); - if (currentContentType == null || "".equals(currentContentType)) { + if (currentContentType == null || currentContentType.isEmpty()) { log.debug("Called set character encoding to " + SecurityUtils.crlf(s) + " on a request without a content type. Character encoding will not be set"); return; } - request.getMultiValueHeaders().putSingle(HttpHeaders.CONTENT_TYPE, appendCharacterEncoding(currentContentType, s)); + request.getMultiValueHeaders().putSingle(HttpHeaders.CONTENT_TYPE, HttpUtils.appendCharacterEncoding(currentContentType, s)); } @@ -347,17 +328,26 @@ public String getContentType() { @Override public String getParameter(String s) { - String queryStringParameter = getFirstQueryParamValue(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive()); - if (queryStringParameter != null) { - return queryStringParameter; - } - String[] bodyParams = getFormBodyParameterCaseInsensitive(s); - if (bodyParams.length == 0) { - return null; - } else { - return bodyParams[0]; + // decode key if ALB + if (request.getRequestSource() == RequestSource.ALB) { + s = decodeValueIfEncoded(s); } + + String queryStringParameter = getFirstQueryParamValue(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive()); + if (queryStringParameter != null) { + if (request.getRequestSource() == RequestSource.ALB) { + queryStringParameter = decodeValueIfEncoded(queryStringParameter); + } + return queryStringParameter; + } + + String[] bodyParams = getFormBodyParameterCaseInsensitive(s); + if (bodyParams.length == 0) { + return null; + } else { + return bodyParams[0]; + } } @@ -367,18 +357,43 @@ public Enumeration getParameterNames() { if (request.getMultiValueQueryStringParameters() == null) { return Collections.enumeration(formParameterNames); } - return Collections.enumeration(Stream.concat(formParameterNames.stream(), - request.getMultiValueQueryStringParameters().keySet().stream()).collect(Collectors.toSet())); + + Set paramNames = request.getMultiValueQueryStringParameters().keySet(); + if (request.getRequestSource() == RequestSource.ALB) { + paramNames = paramNames.stream().map(AwsProxyHttpServletRequest::decodeValueIfEncoded).collect(Collectors.toSet()); + } + + return Collections.enumeration( + Stream.concat(formParameterNames.stream(), paramNames.stream()) + .collect(Collectors.toSet())); } @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(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive()))); + + // decode key if ALB + if (request.getRequestSource() == RequestSource.ALB) { + s = decodeValueIfEncoded(s); + } - values.addAll(Arrays.asList(getFormBodyParameterCaseInsensitive(s))); + List values = getQueryParamValuesAsList(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive()); + + // copy list so we don't modifying the underlying multi-value query params + if (values != null) { + values = new ArrayList<>(values); + } else { + values = new ArrayList<>(); + } + + // decode values if ALB + if (values != null && request.getRequestSource() == RequestSource.ALB) { + values = values.stream().map(AwsHttpServletRequest::decodeValueIfEncoded).collect(Collectors.toList()); + } + values.addAll(Arrays.asList(getFormBodyParameterCaseInsensitive(s))); + if (values.size() == 0) { return null; } else { @@ -389,7 +404,7 @@ public String[] getParameterValues(String s) { @Override public Map getParameterMap() { - return generateParameterMap(request.getMultiValueQueryStringParameters(), config); + return generateParameterMap(request.getMultiValueQueryStringParameters(), config, request.getRequestSource() == RequestSource.ALB); } @@ -414,7 +429,7 @@ public String getServerName() { 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)) { + if (SecurityUtils.isValidHost(hostHeader, request.getRequestContext().getApiId(), request.getRequestContext().getElb(), region)) { return hostHeader; } } @@ -459,48 +474,42 @@ public String getRemoteAddr() { if (request.getRequestContext() == null || request.getRequestContext().getIdentity() == null) { return "127.0.0.1"; } + if (request.getRequestSource().equals(RequestSource.ALB)) { + return Objects.nonNull(request.getHeaders()) ? + request.getHeaders().get(CLIENT_IP_HEADER) : + request.getMultiValueHeaders().getFirst(CLIENT_IP_HEADER); + } return request.getRequestContext().getIdentity().getSourceIp(); } @Override public String getRemoteHost() { - return request.getMultiValueHeaders().getFirst(HttpHeaders.HOST); + String hostHeader; + if (request.getRequestSource().equals(RequestSource.ALB)) { + hostHeader = Objects.nonNull(request.getHeaders()) ? + request.getHeaders().get(HttpHeaders.HOST) : + request.getMultiValueHeaders().getFirst(HttpHeaders.HOST); + } else { + hostHeader = request.getMultiValueHeaders().getFirst(HttpHeaders.HOST); + } + // the host header has the form host:port, so we split the string to get the host part + return Arrays.asList(hostHeader.split(":")).get(0); } @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( - request.getMultiValueHeaders().getFirst(HttpHeaders.ACCEPT_LANGUAGE), ",", ";" - ); - if (values.size() == 0) { - return Locale.getDefault(); - } - return new Locale(values.get(0).getValue()); + List locales = parseAcceptLanguageHeader(request.getMultiValueHeaders().getFirst(HttpHeaders.ACCEPT_LANGUAGE)); + return locales.size() == 0 ? Locale.getDefault() : locales.get(0); } - @Override public Enumeration getLocales() { - List values = this.parseHeaderValue( - request.getMultiValueHeaders().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())); - } - } - + List locales = parseAcceptLanguageHeader(request.getMultiValueHeaders().getFirst(HttpHeaders.ACCEPT_LANGUAGE)); return Collections.enumeration(locales); } - @Override public boolean isSecure() { return securityContext.isSecure(); @@ -513,16 +522,17 @@ public RequestDispatcher getRequestDispatcher(String s) { } - @Override - @Deprecated - public String getRealPath(String s) { - // we are in an archive on a remote server - return null; - } - - @Override public int getRemotePort() { + if (request.getRequestSource().equals(RequestSource.ALB)) { + String portHeader; + portHeader = Objects.nonNull(request.getHeaders()) ? + request.getHeaders().get(PORT_HEADER_NAME) : + request.getMultiValueHeaders().getFirst(PORT_HEADER_NAME); + if (Objects.nonNull(portHeader)) { + return Integer.parseInt(portHeader); + } + } return 0; } @@ -547,7 +557,7 @@ public boolean isAsyncStarted() { @Override public AsyncContext startAsync() throws IllegalStateException { - asyncContext = new AwsAsyncContext(this, response, containerHandler); + asyncContext = new AwsAsyncContext(this, response); setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ASYNC); log.debug("Starting async context for request: " + SecurityUtils.crlf(request.getRequestContext().getRequestId())); return asyncContext; @@ -558,7 +568,7 @@ public AsyncContext startAsync() public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { servletRequest.setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ASYNC); - asyncContext = new AwsAsyncContext((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, containerHandler); + asyncContext = new AwsAsyncContext((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); log.debug("Starting async context for request: " + SecurityUtils.crlf(request.getRequestContext().getRequestId())); return asyncContext; } @@ -572,6 +582,21 @@ public AsyncContext getAsyncContext() { return asyncContext; } + @Override + public String getRequestId() { + return request.getRequestContext().getRequestId(); + } + + @Override + public String getProtocolRequestId() { + return ""; + } + + @Override + public ServletConnection getServletConnection() { + return null; + } + //------------------------------------------------------------- // Methods - Private //------------------------------------------------------------- @@ -580,7 +605,7 @@ private List getHeaderValues(String key) { // special cases for referer and user agent headers List values = new ArrayList<>(); - if (request.getRequestSource() == AwsProxyRequest.RequestSource.API_GATEWAY) { + if (request.getRequestSource() == RequestSource.API_GATEWAY) { if ("referer".equals(key.toLowerCase(Locale.ENGLISH))) { values.add(request.getRequestContext().getIdentity().getCaller()); return values; 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 932041db9..ec56285f7 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java @@ -18,10 +18,10 @@ import com.amazonaws.serverless.proxy.model.ContainerConfig; 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; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.SecurityContext; /** * Simple implementation of the RequestReader interface that receives an AwsProxyRequest 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 de84b5c2d..0e230e9a7 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 @@ -17,12 +17,13 @@ import com.amazonaws.serverless.proxy.ResponseWriter; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; 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.Headers; +import com.amazonaws.serverless.proxy.model.RequestSource; import com.amazonaws.services.lambda.runtime.Context; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; import java.util.Base64; import java.util.HashMap; @@ -73,8 +74,11 @@ public AwsProxyResponse writeResponse(AwsHttpServletResponse containerResponse, awsProxyResponse.setStatusCode(containerResponse.getStatus()); - if (containerResponse.getAwsProxyRequest() != null && containerResponse.getAwsProxyRequest().getRequestSource() == AwsProxyRequest.RequestSource.ALB) { - awsProxyResponse.setStatusDescription(containerResponse.getStatus() + " " + Response.Status.fromStatusCode(containerResponse.getStatus()).getReasonPhrase()); + Status responseStatus = Response.Status.fromStatusCode(containerResponse.getStatus()); + + if (containerResponse.getAwsProxyRequest() != null && containerResponse.getAwsProxyRequest().getRequestSource() == RequestSource.ALB + && responseStatus != null) { + awsProxyResponse.setStatusDescription(containerResponse.getStatus() + " " + responseStatus.getReasonPhrase()); } Timer.stop("SERVLET_RESPONSE_WRITE"); 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 27a352ebf..f314ba0d0 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 @@ -19,9 +19,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @@ -80,7 +80,7 @@ public void forward(ServletRequest servletRequest, ServletResponse servletRespon } if (isNamedDispatcher) { - lambdaContainerHandler.doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, getServlet((HttpServletRequest)servletRequest)); + lambdaContainerHandler.doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, ((AwsServletRegistration)servletRequest.getServletContext().getServletRegistration(dispatchTo)).getServlet()); return; } @@ -148,4 +148,5 @@ void setRequestPath(ServletRequest req, final String destinationPath) { private Servlet getServlet(HttpServletRequest req) { return ((AwsServletContext)lambdaContainerHandler.getServletContext()).getServletForPath(req.getPathInfo()); } + } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestPart.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestPart.java index a6a418c2c..c2d1fa62d 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestPart.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestPart.java @@ -17,7 +17,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import javax.servlet.http.Part; +import jakarta.servlet.http.Part; import java.io.ByteArrayInputStream; import java.io.FileOutputStream; import java.io.IOException; 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 864a9bf10..94dcaf440 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 @@ -17,20 +17,25 @@ import com.amazonaws.serverless.proxy.internal.SecurityUtils; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.activation.MimetypesFileTypeMap; -import javax.servlet.*; -import javax.servlet.descriptor.JspConfigDescriptor; +import jakarta.servlet.*; +import jakarta.servlet.ServletContext; +import jakarta.servlet.descriptor.JspConfigDescriptor; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Paths; import java.util.*; -import java.util.stream.Collectors; /** @@ -44,8 +49,8 @@ public class AwsServletContext //------------------------------------------------------------- // Constants - Public // ------------------------------------------------------------- - public static final int SERVLET_API_MAJOR_VERSION = 3; - public static final int SERVLET_API_MINOR_VERSION = 1; + public static final int SERVLET_API_MAJOR_VERSION = 6; + public static final int SERVLET_API_MINOR_VERSION = 0; public static final String SERVER_INFO = LambdaContainerHandler.SERVER_INFO + "/" + SERVLET_API_MAJOR_VERSION + "." + SERVLET_API_MINOR_VERSION; @@ -58,7 +63,6 @@ public class AwsServletContext private Map initParameters; private AwsLambdaServletContainerHandler containerHandler; private Logger log = LoggerFactory.getLogger(AwsServletContext.class); - private MimetypesFileTypeMap mimeTypes; // lazily loaded in the getMimeType method //------------------------------------------------------------- @@ -79,7 +83,6 @@ public AwsServletContext(AwsLambdaServletContainerHandler containerHandler) { this.servletRegistrations = new HashMap<>(); } - //------------------------------------------------------------- // Implementation - ServletContext //------------------------------------------------------------- @@ -95,6 +98,36 @@ public String getContextPath() { return ""; } + @Override + public void setResponseCharacterEncoding(String encoding) { + throw new UnsupportedOperationException(); + } + + @Override + public String getResponseCharacterEncoding() { + return null; + } + + @Override + public void setRequestCharacterEncoding(String encoding) { + throw new UnsupportedOperationException(); + } + + @Override + public String getRequestCharacterEncoding() { + return null; + } + + @Override + public void setSessionTimeout(int sessionTimeout) { + throw new UnsupportedOperationException(); + } + + @Override + public int getSessionTimeout() { + return 0; + } + @Override public ServletContext getContext(String s) { @@ -129,17 +162,32 @@ public int getEffectiveMinorVersion() { @Override @SuppressFBWarnings("PATH_TRAVERSAL_IN") // suppressing because we are using the getValidFilePath - public String getMimeType(String s) { - if (s == null || !s.contains(".")) { + public String getMimeType(String file) { + if (file == null || !file.contains(".")) { return null; } - if (mimeTypes == null) { - mimeTypes = new MimetypesFileTypeMap(); + + String mimeType = null; + + // may not work on Lambda until mailcap package is present https://github.com/aws/serverless-java-container/pull/504 + try { + mimeType = Files.probeContentType(Paths.get(file)); + } catch (IOException | InvalidPathException e) { + log("unable to probe for content type, will use fallback", e); + } + + if (mimeType == null) { + try { + String mimeTypeGuess = URLConnection.guessContentTypeFromName(new File(file).getName()); + if (mimeTypeGuess !=null) { + mimeType = mimeTypeGuess; + } + } catch (Exception e) { + log("couldn't find a better contentType than " + mimeType + " for file " + file, e); + } } - // TODO: The getContentType method of the MimetypesFileTypeMap returns application/octet-stream - // instead of null when the type cannot be found. We should replace with an implementation that - // loads the System mime types ($JAVA_HOME/lib/mime.types - return mimeTypes.getContentType(s); + + return mimeType; } @@ -171,14 +219,7 @@ public RequestDispatcher getRequestDispatcher(String s) { @Override public RequestDispatcher getNamedDispatcher(String s) { - throw new UnsupportedOperationException(); - } - - - @Override - @Deprecated - public Servlet getServlet(String s) throws ServletException { - return servletRegistrations.get(s).getServlet(); + return new AwsProxyRequestDispatcher(s, true, containerHandler); } public Servlet getServletForPath(String path) { @@ -206,38 +247,12 @@ public Servlet getServletForPath(String path) { return null; } - - @Override - @Deprecated - public Enumeration getServlets() { - return Collections.enumeration(servletRegistrations.entrySet().stream() - .map((e) -> { - return e.getValue().getServlet(); - } ) - .collect(Collectors.toList())); - } - - - @Override - @Deprecated - public Enumeration getServletNames() { - return Collections.enumeration(servletRegistrations.keySet()); - } - - @Override public void log(String s) { log.info(SecurityUtils.encode(s)); } - @Override - @Deprecated - public void log(Exception e, String s) { - log.error(SecurityUtils.encode(s), e); - } - - @Override public void log(String s, Throwable throwable) { log.error(SecurityUtils.encode(s), throwable); @@ -345,6 +360,11 @@ public ServletRegistration.Dynamic addServlet(String s, Class } } + @Override + public ServletRegistration.Dynamic addJspFile(String s, String s1) { + throw new UnsupportedOperationException(); + } + @Override public T createServlet(Class aClass) throws ServletException { @@ -519,9 +539,7 @@ public JspConfigDescriptor getJspConfigDescriptor() { @Override public ClassLoader getClassLoader() { - // for the time being we return the default class loader. We may want to let developers override this int the - // future. - return ClassLoader.getSystemClassLoader(); + return getClass().getClassLoader(); } 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 552e75a89..eb155a364 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 @@ -16,8 +16,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.ReadListener; -import javax.servlet.ServletInputStream; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; import java.io.IOException; import java.io.InputStream; 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 021503c13..9fdc3b3b6 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 @@ -14,7 +14,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import javax.servlet.*; +import jakarta.servlet.*; import java.util.*; /** diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/CookieProcessor.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/CookieProcessor.java new file mode 100644 index 000000000..c59dc806a --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/CookieProcessor.java @@ -0,0 +1,23 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import jakarta.servlet.http.Cookie; + +public interface CookieProcessor { + /** + * Parse the provided cookie header value into an array of Cookie objects. + * + * @param cookieHeader The cookie header value string to parse, e.g., "SID=31d4d96e407aad42; lang=en-US" + * @return An array of Cookie objects parsed from the cookie header value + */ + Cookie[] parseCookieHeader(String cookieHeader); + + /** + * Generate the Set-Cookie HTTP header value for the given Cookie. + * + * @param cookie The cookie for which the header will be generated + * @return The header value in a form that can be added directly to the response + */ + String generateHeader(Cookie cookie); + + +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java index bec7a8f4b..b45eed4a1 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java @@ -16,7 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.*; +import jakarta.servlet.*; import java.io.IOException; import java.util.ArrayList; 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 92ff55daf..4917b4aaa 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,8 +14,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Collections; @@ -26,7 +26,7 @@ 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, + * This object is in charge of matching a servlet request to a set of filters, creating the filter chain for a request, * and cache filter chains that were already loaded for re-use. This object should be used by the framework-specific * implementations that use the HttpServletRequest and HttpServletResponse objects. * @@ -132,7 +132,7 @@ FilterChainHolder getFilterChain(final HttpServletRequest request, Servlet servl chainHolder.addFilter(new FilterHolder(new ServletExecutionFilter(servletRegistration), servletContext)); } - putFilterChainCache(type, targetPath, chainHolder); + putFilterChainCache(type, targetPath, servlet, chainHolder); // update total filter size if (filtersSize != registrations.size()) { filtersSize = registrations.size(); @@ -151,13 +151,16 @@ FilterChainHolder getFilterChain(final HttpServletRequest request, Servlet servl * initialized with the cached list of {@link FilterHolder} objects * @param type The dispatcher type for the incoming request * @param targetPath The request path - this is extracted with the getPath method of the request object - * @param servlet Servlet to put at the end of the chain (optional). + * @param servlet The final servlet in the filter chain (if any) * @return A populated FilterChainHolder */ private FilterChainHolder getFilterChainCache(final DispatcherType type, final String targetPath, Servlet servlet) { TargetCacheKey key = new TargetCacheKey(); key.setDispatcherType(type); key.setTargetPath(targetPath); + if (servlet != null) { + key.setServletName(servlet.getServletConfig().getServletName()); + } if (!filterCache.containsKey(key)) { return null; @@ -174,12 +177,16 @@ private FilterChainHolder getFilterChainCache(final DispatcherType type, final S * method to retry this. * @param type DispatcherType from the incoming request * @param targetPath The target path in the API - * @param holder The FilterChainHolder object to save in the cache + * @param servlet The final servlet in the filter chain (if any) + * @param holder The FilterChainHolder object to save in the cache */ - private void putFilterChainCache(final DispatcherType type, final String targetPath, final FilterChainHolder holder) { + private void putFilterChainCache(final DispatcherType type, final String targetPath, Servlet servlet, final FilterChainHolder holder) { TargetCacheKey key = new TargetCacheKey(); key.setDispatcherType(type); key.setTargetPath(targetPath); + if (servlet != null) { + key.setServletName(servlet.getServletConfig().getServletName()); + } // we couldn't compute the hash code because either the target path or dispatcher type were null if (key.hashCode() == -1) { @@ -256,6 +263,7 @@ protected static class TargetCacheKey { private String targetPath; private DispatcherType dispatcherType; + private String servletName; //------------------------------------------------------------- @@ -295,10 +303,15 @@ public int hashCode() { } hashString += ":" + hashDispatcher; + if (servletName != null) { + hashString += ":" + servletName; + } + return hashString.hashCode(); } + @Override public boolean equals(Object key) { if (key == null) { @@ -324,6 +337,11 @@ void setTargetPath(String targetPath) { void setDispatcherType(DispatcherType dispatcherType) { this.dispatcherType = dispatcherType; } + public void setServletName(String servletName) { + this.servletName = servletName; + } + + } @SuppressFBWarnings("URF_UNREAD_FIELD") 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 987dff14c..c3a4cdb81 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 @@ -14,9 +14,9 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import javax.servlet.*; -import javax.servlet.annotation.WebFilter; -import javax.servlet.annotation.WebInitParam; +import jakarta.servlet.*; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.annotation.WebInitParam; import java.util.*; /** 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 caead2aaf..a2c1c73ff 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 @@ -18,7 +18,7 @@ import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; @@ -92,11 +92,11 @@ protected void validate() throws ContainerInitializationException { * @return A populated builder */ public Builder defaultProxy() { - initializationWrapper(new InitializationWrapper()) + initializationWrapper(new AsyncInitializationWrapper()) .requestReader((RequestReader) new AwsProxyHttpServletRequestReader()) .responseWriter((ResponseWriter) new AwsProxyHttpServletResponseWriter()) .securityContextWriter((SecurityContextWriter) new AwsProxySecurityContextWriter()) - .exceptionHandler((ExceptionHandler) new AwsProxyExceptionHandler()) + .exceptionHandler(defaultExceptionHandler()) .requestTypeClass((Class) AwsProxyRequest.class) .responseTypeClass((Class) AwsProxyResponse.class); return self(); @@ -108,17 +108,21 @@ public Builder defaultProxy() { * @return A populated builder */ public Builder defaultHttpApiV2Proxy() { - initializationWrapper(new InitializationWrapper()) + initializationWrapper(new AsyncInitializationWrapper()) .requestReader((RequestReader) new AwsHttpApiV2HttpServletRequestReader()) .responseWriter((ResponseWriter) new AwsProxyHttpServletResponseWriter(true)) .securityContextWriter((SecurityContextWriter) new AwsHttpApiV2SecurityContextWriter()) - .exceptionHandler((ExceptionHandler) new AwsProxyExceptionHandler()) + .exceptionHandler(defaultExceptionHandler()) .requestTypeClass((Class) HttpApiV2ProxyRequest.class) .responseTypeClass((Class) AwsProxyResponse.class); return self(); } + protected ExceptionHandler defaultExceptionHandler() { + return (ExceptionHandler) new AwsProxyExceptionHandler(); + } + /** * Sets the initialization wrapper to be used by the {@link ServletLambdaContainerHandlerBuilder#buildAndInitialize()} * method to start the framework implementations @@ -165,7 +169,7 @@ public Builder responseTypeClass(Class responseType) { /** * 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()}. + * @deprecated As of release 2.0.0 this method is deprecated. Initializer is always async if running in on-demand. * @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 */ @@ -178,6 +182,7 @@ public Builder asyncInit(long actualStartTime) { /** * Uses a new {@link AsyncInitializationWrapper} with the no-parameter constructor that takes the actual JVM * start time + * @deprecated As of release 2.0.0 this method is deprecated. Initializer is always async if running in on-demand. * @return A builder configured to use an async initializer */ public Builder asyncInit() { diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java index c69b39c91..aadb26efd 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java @@ -15,10 +15,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.*; -import javax.servlet.annotation.WebFilter; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.*; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java index 440421a71..eeaaf4a6f 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java @@ -29,10 +29,11 @@ public class AwsProxyRequest { //------------------------------------------------------------- private String body; + private String version; private String resource; private AwsProxyRequestContext requestContext; private MultiValuedTreeMap multiValueQueryStringParameters; - private Map queryStringParameters; + private Map queryStringParameters; private Headers multiValueHeaders; private SingleValueHeaders headers; private Map pathParameters; @@ -65,7 +66,7 @@ public String getQueryString() { for (String val : this.getMultiValueQueryStringParameters().get(key)) { String separator = params.length() == 0 ? "?" : "&"; - params.append(separator + key + "=" + val); + params.append(separator).append(key).append("=").append(val); } } @@ -95,6 +96,13 @@ public String getResource() { return resource; } + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } public void setResource(String resource) { this.resource = resource; @@ -192,9 +200,4 @@ public boolean isBase64Encoded() { public void setIsBase64Encoded(boolean base64Encoded) { isBase64Encoded = base64Encoded; } - - public static enum RequestSource { - ALB, - API_GATEWAY - } } 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 4fff028c4..2cf6d77a6 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 @@ -34,16 +34,21 @@ public class HttpApiV2AuthorizerMap extends HashMap { private static final String JWT_KEY = "jwt"; private static final String LAMBDA_KEY = "lambda"; + private static final String IAM_KEY = "iam"; private static final long serialVersionUID = 42L; public HttpApiV2JwtAuthorizer getJwtAuthorizer() { - return (HttpApiV2JwtAuthorizer)get(JWT_KEY); + return (HttpApiV2JwtAuthorizer) get(JWT_KEY); } public Map getLambdaAuthorizerContext() { return (Map) get(LAMBDA_KEY); } + public HttpApiV2IamAuthorizer getIamAuthorizer() { + return (HttpApiV2IamAuthorizer) get(IAM_KEY); + } + public boolean isJwt() { return containsKey(JWT_KEY); } @@ -52,10 +57,18 @@ public boolean isLambda() { return containsKey(LAMBDA_KEY); } + public boolean isIam() { + return containsKey(IAM_KEY); + } + public void putJwtAuthorizer(HttpApiV2JwtAuthorizer jwt) { put(JWT_KEY, jwt); } + public void putIamAuthorizer(HttpApiV2IamAuthorizer iam) { + put(IAM_KEY, iam); + } + public static class HttpApiV2AuthorizerDeserializer extends StdDeserializer { private static final long serialVersionUID = 42L; @@ -64,11 +77,13 @@ public HttpApiV2AuthorizerDeserializer() { } @Override - public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException, JsonProcessingException { HttpApiV2AuthorizerMap map = new HttpApiV2AuthorizerMap(); JsonNode node = jsonParser.getCodec().readTree(jsonParser); if (node.has(JWT_KEY)) { - HttpApiV2JwtAuthorizer authorizer = LambdaContainerHandler.getObjectMapper().treeToValue(node.get(JWT_KEY), HttpApiV2JwtAuthorizer.class); + HttpApiV2JwtAuthorizer authorizer = LambdaContainerHandler.getObjectMapper() + .treeToValue(node.get(JWT_KEY), HttpApiV2JwtAuthorizer.class); map.putJwtAuthorizer(authorizer); } if (node.has(LAMBDA_KEY)) { @@ -76,6 +91,11 @@ public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, Deserialization TypeFactory.defaultInstance().constructMapType(HashMap.class, String.class, Object.class)); map.put(LAMBDA_KEY, context); } + if (node.has(IAM_KEY)) { + HttpApiV2IamAuthorizer iam_authorizer = LambdaContainerHandler.getObjectMapper() + .treeToValue(node.get(IAM_KEY), HttpApiV2IamAuthorizer.class); + map.putIamAuthorizer(iam_authorizer); + } // we ignore other, unknown values return map; } @@ -89,7 +109,8 @@ public HttpApiV2AuthorizerSerializer() { } @Override - public void serialize(HttpApiV2AuthorizerMap httpApiV2AuthorizerMap, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + public void serialize(HttpApiV2AuthorizerMap httpApiV2AuthorizerMap, JsonGenerator jsonGenerator, + SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeStartObject(); if (httpApiV2AuthorizerMap.isJwt()) { jsonGenerator.writeObjectField(JWT_KEY, httpApiV2AuthorizerMap.getJwtAuthorizer()); @@ -97,6 +118,9 @@ public void serialize(HttpApiV2AuthorizerMap httpApiV2AuthorizerMap, JsonGenerat if (httpApiV2AuthorizerMap.isLambda()) { jsonGenerator.writeObjectField(LAMBDA_KEY, httpApiV2AuthorizerMap.getLambdaAuthorizerContext()); } + if (httpApiV2AuthorizerMap.isIam()) { + jsonGenerator.writeObjectField(IAM_KEY, httpApiV2AuthorizerMap.get(IAM_KEY)); + } jsonGenerator.writeEndObject(); } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2IamAuthorizer.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2IamAuthorizer.java new file mode 100644 index 000000000..d2a0952ec --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2IamAuthorizer.java @@ -0,0 +1,68 @@ +package com.amazonaws.serverless.proxy.model; + +public class HttpApiV2IamAuthorizer { + public String accessKey; + public String accountId; + public String callerId; + public String cognitoIdentity; + public String principalOrgId; + public String userArn; + public String userId; + + public String getAccessKey() { + return accessKey; + } + + public String getAccountId() { + return accountId; + } + + public String getCallerId() { + return callerId; + } + + public String getCognitoIdentity() { + return cognitoIdentity; + } + + public String getPrincipalOrgId() { + return principalOrgId; + } + + public String getUserArn() { + return userArn; + } + + public String getUserId() { + return userId; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public void setCallerId(String callerId) { + this.callerId = callerId; + } + + public void setCognitoIdentity(String cognitoIdentity) { + this.cognitoIdentity = cognitoIdentity; + } + + public void setPrincipalOrgId(String principalOrgId) { + this.principalOrgId = principalOrgId; + } + + public void setUserArn(String userArn) { + this.userArn = userArn; + } + + public void setUserId(String userId) { + this.userId = userId; + } + +} \ No newline at end of file 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 c50298eea..023236cc7 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 @@ -13,9 +13,12 @@ package com.amazonaws.serverless.proxy.model; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; public class HttpApiV2ProxyRequest { private String version; @@ -127,4 +130,12 @@ public HttpApiV2ProxyRequestContext getRequestContext() { public void setRequestContext(HttpApiV2ProxyRequestContext requestContext) { this.requestContext = requestContext; } + + @JsonIgnore + public RequestSource getRequestSource() { + return Optional.ofNullable(getRequestContext()) + .map(HttpApiV2ProxyRequestContext::getElb) + .map(albContext -> RequestSource.ALB) + .orElse(RequestSource.API_GATEWAY); + } } 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 738f692f6..e5a5b9d2d 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 @@ -25,6 +25,7 @@ public class HttpApiV2ProxyRequestContext { private String stage; private String time; private long timeEpoch; + private AlbContext elb; private HttpApiV2HttpContext http; private HttpApiV2AuthorizerMap authorizer; @@ -117,4 +118,13 @@ public void setAuthorizer(HttpApiV2AuthorizerMap authorizer) { this.authorizer = authorizer; } + public AlbContext getElb() { + return this.elb; + } + + public void setElb(AlbContext context) { + this.elb = context; + } + + } 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 feeab6f2b..ad0593a1f 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 @@ -14,7 +14,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import javax.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.MultivaluedMap; import java.io.Serializable; import java.util.ArrayList; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/RequestSource.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/RequestSource.java new file mode 100644 index 000000000..c819fdcc0 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/RequestSource.java @@ -0,0 +1,18 @@ +/* + * Copyright 2022 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 enum RequestSource { + ALB, + API_GATEWAY +} \ No newline at end of file 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 index 3b8a7306c..74a6b8038 100644 --- 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 @@ -1,24 +1,24 @@ package com.amazonaws.serverless.proxy; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.lang.management.ManagementFactory; import java.time.Clock; import java.time.Instant; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class AsyncInitializationWrapperTest { @Test - public void initCreate_noStartTime_setsCurrentTime() { + 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 { + void initCreate_withStartTime_storesCustomStartTime() throws InterruptedException { long initTime = Instant.now().toEpochMilli(); Thread.sleep(500); AsyncInitializationWrapper init = new AsyncInitializationWrapper(initTime); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java index cf86c57a6..012827e40 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java @@ -8,18 +8,18 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import static org.junit.Assert.*; -import static org.mockito.Matchers.any; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import javax.ws.rs.InternalServerErrorException; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.InternalServerErrorException; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; import java.io.*; @@ -31,14 +31,14 @@ public class AwsProxyExceptionHandlerTest { private ObjectMapper objectMapper; - @Before + @BeforeEach public void setUp() { exceptionHandler = new AwsProxyExceptionHandler(); objectMapper = new ObjectMapper(); } @Test - public void typedHandle_InvalidRequestEventException_500State() { + void typedHandle_InvalidRequestEventException_500State() { AwsProxyResponse resp = exceptionHandler.handle(new InvalidRequestEventException(INVALID_REQUEST_MESSAGE, null)); assertNotNull(resp); @@ -46,7 +46,7 @@ public void typedHandle_InvalidRequestEventException_500State() { } @Test - public void typedHandle_InvalidRequestEventException_responseString() + void typedHandle_InvalidRequestEventException_responseString() throws JsonProcessingException { AwsProxyResponse resp = exceptionHandler.handle(new InvalidRequestEventException(INVALID_REQUEST_MESSAGE, null)); @@ -56,7 +56,7 @@ public void typedHandle_InvalidRequestEventException_responseString() } @Test - public void typedHandle_InvalidRequestEventException_jsonContentTypeHeader() { + void typedHandle_InvalidRequestEventException_jsonContentTypeHeader() { AwsProxyResponse resp = exceptionHandler.handle(new InvalidRequestEventException(INVALID_REQUEST_MESSAGE, null)); assertNotNull(resp); @@ -65,7 +65,7 @@ public void typedHandle_InvalidRequestEventException_jsonContentTypeHeader() { } @Test - public void typedHandle_InvalidResponseObjectException_502State() { + void typedHandle_InvalidResponseObjectException_502State() { AwsProxyResponse resp = exceptionHandler.handle(new InvalidResponseObjectException(INVALID_RESPONSE_MESSAGE, null)); assertNotNull(resp); @@ -73,7 +73,7 @@ public void typedHandle_InvalidResponseObjectException_502State() { } @Test - public void typedHandle_InvalidResponseObjectException_responseString() + void typedHandle_InvalidResponseObjectException_responseString() throws JsonProcessingException { AwsProxyResponse resp = exceptionHandler.handle(new InvalidResponseObjectException(INVALID_RESPONSE_MESSAGE, null)); @@ -83,7 +83,7 @@ public void typedHandle_InvalidResponseObjectException_responseString() } @Test - public void typedHandle_InvalidResponseObjectException_jsonContentTypeHeader() { + void typedHandle_InvalidResponseObjectException_jsonContentTypeHeader() { AwsProxyResponse resp = exceptionHandler.handle(new InvalidResponseObjectException(INVALID_RESPONSE_MESSAGE, null)); assertNotNull(resp); @@ -92,7 +92,7 @@ public void typedHandle_InvalidResponseObjectException_jsonContentTypeHeader() { } @Test - public void typedHandle_InternalServerErrorException_500State() { + void typedHandle_InternalServerErrorException_500State() { // Needed to mock InternalServerErrorException because it leverages RuntimeDelegate to set an internal // response object. InternalServerErrorException mockInternalServerErrorException = Mockito.mock(InternalServerErrorException.class); @@ -105,7 +105,7 @@ public void typedHandle_InternalServerErrorException_500State() { } @Test - public void typedHandle_InternalServerErrorException_responseString() + void typedHandle_InternalServerErrorException_responseString() throws JsonProcessingException { InternalServerErrorException mockInternalServerErrorException = Mockito.mock(InternalServerErrorException.class); Mockito.when(mockInternalServerErrorException.getMessage()).thenReturn(INTERNAL_SERVER_ERROR_MESSAGE); @@ -118,7 +118,7 @@ public void typedHandle_InternalServerErrorException_responseString() } @Test - public void typedHandle_InternalServerErrorException_jsonContentTypeHeader() { + void typedHandle_InternalServerErrorException_jsonContentTypeHeader() { InternalServerErrorException mockInternalServerErrorException = Mockito.mock(InternalServerErrorException.class); Mockito.when(mockInternalServerErrorException.getMessage()).thenReturn(INTERNAL_SERVER_ERROR_MESSAGE); @@ -130,7 +130,7 @@ public void typedHandle_InternalServerErrorException_jsonContentTypeHeader() { } @Test - public void typedHandle_NullPointerException_responseObject() + void typedHandle_NullPointerException_responseObject() throws JsonProcessingException { AwsProxyResponse resp = exceptionHandler.handle(new NullPointerException()); @@ -143,7 +143,7 @@ public void typedHandle_NullPointerException_responseObject() } @Test - public void streamHandle_InvalidRequestEventException_500State() + void streamHandle_InvalidRequestEventException_500State() throws IOException { ByteArrayOutputStream respStream = new ByteArrayOutputStream(); exceptionHandler.handle(new InvalidRequestEventException(INVALID_REQUEST_MESSAGE, null), respStream); @@ -156,7 +156,7 @@ public void streamHandle_InvalidRequestEventException_500State() } @Test - public void streamHandle_InvalidRequestEventException_responseString() + void streamHandle_InvalidRequestEventException_responseString() throws IOException { ByteArrayOutputStream respStream = new ByteArrayOutputStream(); exceptionHandler.handle(new InvalidRequestEventException(INVALID_REQUEST_MESSAGE, null), respStream); @@ -170,7 +170,7 @@ public void streamHandle_InvalidRequestEventException_responseString() } @Test - public void streamHandle_InvalidRequestEventException_jsonContentTypeHeader() + void streamHandle_InvalidRequestEventException_jsonContentTypeHeader() throws IOException { ByteArrayOutputStream respStream = new ByteArrayOutputStream(); exceptionHandler.handle(new InvalidRequestEventException(INVALID_REQUEST_MESSAGE, null), respStream); @@ -184,7 +184,7 @@ public void streamHandle_InvalidRequestEventException_jsonContentTypeHeader() } @Test - public void streamHandle_InvalidResponseObjectException_502State() + void streamHandle_InvalidResponseObjectException_502State() throws IOException { ByteArrayOutputStream respStream = new ByteArrayOutputStream(); exceptionHandler.handle(new InvalidResponseObjectException(INVALID_RESPONSE_MESSAGE, null), respStream); @@ -197,7 +197,7 @@ public void streamHandle_InvalidResponseObjectException_502State() } @Test - public void streamHandle_InvalidResponseObjectException_responseString() + void streamHandle_InvalidResponseObjectException_responseString() throws IOException { ByteArrayOutputStream respStream = new ByteArrayOutputStream(); exceptionHandler.handle(new InvalidResponseObjectException(INVALID_RESPONSE_MESSAGE, null), respStream); @@ -211,7 +211,7 @@ public void streamHandle_InvalidResponseObjectException_responseString() } @Test - public void streamHandle_InvalidResponseObjectException_jsonContentTypeHeader() + void streamHandle_InvalidResponseObjectException_jsonContentTypeHeader() throws IOException { ByteArrayOutputStream respStream = new ByteArrayOutputStream(); exceptionHandler.handle(new InvalidResponseObjectException(INVALID_RESPONSE_MESSAGE, null), respStream); @@ -225,17 +225,17 @@ public void streamHandle_InvalidResponseObjectException_jsonContentTypeHeader() } @Test - public void errorMessage_InternalServerError_staticString() { + void errorMessage_InternalServerError_staticString() { assertEquals("Internal Server Error", AwsProxyExceptionHandler.INTERNAL_SERVER_ERROR); } @Test - public void errorMessage_GatewayTimeout_staticString() { - assertEquals("Gateway timeout", AwsProxyExceptionHandler.GATEWAY_TIMEOUT_ERROR); + void errorMessage_GatewayTimeout_staticString() { + assertEquals("Gateway Timeout", AwsProxyExceptionHandler.GATEWAY_TIMEOUT_ERROR); } @Test - public void getErrorJson_ErrorModel_validJson() + void getErrorJson_ErrorModel_validJson() throws IOException { String output = exceptionHandler.getErrorJson(INVALID_RESPONSE_MESSAGE); assertNotNull(output); @@ -245,7 +245,7 @@ public void getErrorJson_ErrorModel_validJson() } @Test - public void getErrorJson_JsonParsinException_validJson() + void getErrorJson_JsonParsinException_validJson() throws IOException { ObjectMapper mockMapper = mock(ObjectMapper.class); JsonProcessingException exception = mock(JsonProcessingException.class); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriterTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriterTest.java index 6255344a9..8a2134f48 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriterTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriterTest.java @@ -1,35 +1,34 @@ package com.amazonaws.serverless.proxy; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.services.lambda.runtime.Context; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import javax.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.SecurityContext; import java.lang.reflect.Method; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class AwsProxySecurityContextWriterTest { private AwsProxySecurityContextWriter writer; - @Before + @BeforeEach public void setUp() { writer = new AwsProxySecurityContextWriter(); } @Test - public void write_returnClass_securityContext() + void write_returnClass_securityContext() throws NoSuchMethodException { Method writeMethod = writer.getClass().getMethod("writeSecurityContext", AwsProxyRequest.class, Context.class); assertEquals(SecurityContext.class, writeMethod.getReturnType()); } @Test - public void write_noAuth_emptySecurityContext() { + void write_noAuth_emptySecurityContext() { AwsProxyRequest request = new AwsProxyRequestBuilder("/test").build(); SecurityContext context = writer.writeSecurityContext(request, null); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/RequestReaderTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/RequestReaderTest.java index fb0d25f41..3afe72f94 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/RequestReaderTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/RequestReaderTest.java @@ -4,9 +4,9 @@ import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class RequestReaderTest { @@ -17,14 +17,14 @@ public class RequestReaderTest { private static final AwsProxyHttpServletRequestReader requestReader = new AwsProxyHttpServletRequestReader(); @Test - public void defaultConfig_doNotStripBasePath() { + void defaultConfig_doNotStripBasePath() { ContainerConfig config = ContainerConfig.defaultConfig(); assertFalse(config.isStripBasePath()); assertNull(config.getServiceBasePath()); } @Test - public void setServiceBasePath_addSlashes() { + void setServiceBasePath_addSlashes() { ContainerConfig config = new ContainerConfig(); config.setServiceBasePath(BASE_PATH_MAPPING); @@ -35,7 +35,7 @@ public void setServiceBasePath_addSlashes() { } @Test - public void requestReader_stripBasePath() { + void requestReader_stripBasePath() { ContainerConfig config = ContainerConfig.defaultConfig(); String requestPath = "/" + BASE_PATH_MAPPING + ORDERS_URL; @@ -55,7 +55,7 @@ public void requestReader_stripBasePath() { } @Test - public void requestReader_doubleBasePath() { + void requestReader_doubleBasePath() { ContainerConfig config = ContainerConfig.defaultConfig(); config.setStripBasePath(true); config.setServiceBasePath(BASE_PATH_MAPPING); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/ResponseWriterTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/ResponseWriterTest.java index 863ea583f..609fb0fdb 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/ResponseWriterTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/ResponseWriterTest.java @@ -1,16 +1,15 @@ package com.amazonaws.serverless.proxy; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.amazonaws.serverless.exceptions.InvalidResponseObjectException; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; import com.amazonaws.services.lambda.runtime.Context; -import org.junit.Test; - -import javax.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.Test; -import java.nio.ByteBuffer; +import jakarta.servlet.http.HttpServletRequest; -import static junit.framework.TestCase.assertTrue; -import static org.junit.Assert.assertFalse; public class ResponseWriterTest { private static int[][] NAUGHTY_STRINGS = { 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 index 8b10520c3..d6af5112e 100644 --- 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 @@ -10,13 +10,13 @@ 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 org.apache.hc.client5.http.impl.classic.RequestAbortedException; +import org.junit.jupiter.api.Test; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.concurrent.CountDownLatch; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class LambdaContainerHandlerTest { private boolean isRuntimeException = false; @@ -29,7 +29,7 @@ public class LambdaContainerHandlerTest { ); @Test - public void throwRuntime_returnsUnwrappedException() { + void throwRuntime_returnsUnwrappedException() { try { isRuntimeException = true; throwException = true; @@ -44,7 +44,7 @@ public void throwRuntime_returnsUnwrappedException() { } @Test - public void throwNonRuntime_returnsWrappedException() { + void throwNonRuntime_returnsWrappedException() { try { isRuntimeException = false; throwException = true; @@ -61,7 +61,7 @@ public void throwNonRuntime_returnsWrappedException() { } @Test - public void noException_returnsResponse() { + void noException_returnsResponse() { throwException = false; LambdaContainerHandler.getContainerConfig().setDisableExceptionMapper(false); AwsProxyResponse resp = handler.proxy(new AwsProxyRequestBuilder("/test", "GET").build(), new MockLambdaContext()); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/SecurityUtilsTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/SecurityUtilsTest.java index 2e8fb5c27..acf48d9c1 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/SecurityUtilsTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/SecurityUtilsTest.java @@ -1,13 +1,11 @@ package com.amazonaws.serverless.proxy.internal; -import org.junit.Test; - import java.util.HashMap; import java.util.Map; -import static junit.framework.TestCase.fail; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; public class SecurityUtilsTest { diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContextTest.java index db1b9c106..ed89bf86c 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContextTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContextTest.java @@ -2,7 +2,7 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.security.Principal; @@ -10,7 +10,7 @@ import static com.amazonaws.serverless.proxy.internal.jaxrs.AwsProxySecurityContext.ALB_IDENTITY_HEADER; import static com.amazonaws.serverless.proxy.internal.jaxrs.AwsProxySecurityContext.AUTH_SCHEME_COGNITO_POOL; import static com.amazonaws.serverless.proxy.internal.jaxrs.AwsProxySecurityContext.AUTH_SCHEME_CUSTOM; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class AwsProxySecurityContextTest { private static final String CLAIM_KEY = "custom:claim"; @@ -27,21 +27,21 @@ public class AwsProxySecurityContextTest { .build(); @Test - public void localVars_constructor_nullValues() { + void localVars_constructor_nullValues() { AwsProxySecurityContext context = new AwsProxySecurityContext(null, null); assertNull(context.getEvent()); assertNull(context.getLambdaContext()); } @Test - public void localVars_constructor_ValidRequest() { + void localVars_constructor_ValidRequest() { AwsProxySecurityContext context = new AwsProxySecurityContext(null, REQUEST_NO_AUTH); assertEquals(REQUEST_NO_AUTH, context.getEvent()); assertNull(context.getLambdaContext()); } @Test - public void alb_noAuth_expectEmptyScheme() { + void alb_noAuth_expectEmptyScheme() { AwsProxySecurityContext context = new AwsProxySecurityContext(null, ALB_REQUEST_NO_AUTH); assertEquals(ALB_REQUEST_NO_AUTH, context.getEvent()); assertNull(context.getLambdaContext()); @@ -50,21 +50,21 @@ public void alb_noAuth_expectEmptyScheme() { } @Test - public void authScheme_getAuthenticationScheme_userPool() { + void authScheme_getAuthenticationScheme_userPool() { AwsProxySecurityContext context = new AwsProxySecurityContext(null, REQUEST_COGNITO_USER_POOL); assertNotNull(context.getAuthenticationScheme()); assertEquals(AUTH_SCHEME_COGNITO_POOL, context.getAuthenticationScheme()); } @Test - public void authScheme_getPrincipal_userPool() { + void authScheme_getPrincipal_userPool() { AwsProxySecurityContext context = new AwsProxySecurityContext(null, REQUEST_COGNITO_USER_POOL); assertEquals(AUTH_SCHEME_COGNITO_POOL, context.getAuthenticationScheme()); assertEquals(COGNITO_IDENTITY_ID, context.getUserPrincipal().getName()); } @Test - public void alb_cognitoAuth_expectCustomSchemeAndCorrectPrincipal() { + void alb_cognitoAuth_expectCustomSchemeAndCorrectPrincipal() { AwsProxySecurityContext context = new AwsProxySecurityContext(null, ALB_REQUEST_COGNITO_USER_POOL); assertTrue(context.isSecure()); assertEquals(AUTH_SCHEME_CUSTOM, context.getAuthenticationScheme()); @@ -72,7 +72,7 @@ public void alb_cognitoAuth_expectCustomSchemeAndCorrectPrincipal() { } @Test - public void userPool_getClaims_retrieveCustomClaim() { + void userPool_getClaims_retrieveCustomClaim() { AwsProxySecurityContext context = new AwsProxySecurityContext(null, REQUEST_COGNITO_USER_POOL); Principal userPrincipal = context.getUserPrincipal(); assertNotNull(userPrincipal.getName()); 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 index ae2dcdb53..a4c6aa311 100644 --- 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 @@ -3,12 +3,12 @@ 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 org.junit.jupiter.api.Test; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.SecurityContext; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class HttpApiV2SecurityContextTest { private static final String JWT_SUB_VALUE = "1234567890"; @@ -24,7 +24,7 @@ public class HttpApiV2SecurityContextTest { AwsHttpApiV2SecurityContextWriter contextWriter = new AwsHttpApiV2SecurityContextWriter(); @Test - public void getAuthenticationScheme_nullAuth_nullSchema() { + void getAuthenticationScheme_nullAuth_nullSchema() { SecurityContext ctx = contextWriter.writeSecurityContext(EMPTY_AUTH, null); assertNull(ctx.getAuthenticationScheme()); assertNull(ctx.getUserPrincipal()); @@ -32,7 +32,7 @@ public void getAuthenticationScheme_nullAuth_nullSchema() { } @Test - public void getAuthenticationScheme_jwtAuth_correctSchema() { + void getAuthenticationScheme_jwtAuth_correctSchema() { SecurityContext ctx = contextWriter.writeSecurityContext(BASIC_AUTH, null); assertEquals(AwsHttpApiV2SecurityContext.AUTH_SCHEME_JWT, ctx.getAuthenticationScheme()); assertTrue(ctx.isSecure()); @@ -40,7 +40,7 @@ public void getAuthenticationScheme_jwtAuth_correctSchema() { } @Test - public void getPrincipal_parseJwt_returnsSub() { + void getPrincipal_parseJwt_returnsSub() { SecurityContext ctx = contextWriter.writeSecurityContext(JWT_AUTH, null); assertEquals(AwsHttpApiV2SecurityContext.AUTH_SCHEME_JWT, ctx.getAuthenticationScheme()); assertTrue(ctx.isSecure()); 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 0d12a5116..69b2d7985 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 @@ -4,21 +4,20 @@ import com.amazonaws.serverless.proxy.model.ApiGatewayRequestIdentity; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.time.Clock; 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; -import static org.mockito.Matchers.eq; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -31,7 +30,7 @@ public class ApacheCombinedServletLogFormatterTest { private AwsProxyRequest proxyRequest; private AwsProxyRequestContext context; - @Before + @BeforeEach public void setup() { proxyRequest = new AwsProxyRequest(); Clock fixedClock = Clock.fixed(Instant.ofEpochSecond(665888523L), ZoneId.of("UTC")); @@ -48,40 +47,40 @@ public void setup() { sut = new ApacheCombinedServletLogFormatter(fixedClock); } - @Test - public void logsCurrentTimeWhenContextNull() { - // given - proxyRequest.setRequestContext(null); + @Test + void logsCurrentTimeWhenContextNull() { + // given + proxyRequest.setRequestContext(null); - // when - String actual = sut.format(mockServletRequest, mockServletResponse, null); + // when + String actual = sut.format(mockServletRequest, mockServletResponse, null); - // then - assertThat(actual, containsString("[07/02/1991:01:02:03Z]")); - } + // then + assertThat(actual, containsString("[07/02/1991:01:02:03Z]")); + } - @Test - public void logsCurrentTimeWhenRequestTimeZero() { - // given - context.setRequestTimeEpoch(0); + @Test + void logsCurrentTimeWhenRequestTimeZero() { + // given + context.setRequestTimeEpoch(0); - // when - String actual = sut.format(mockServletRequest, mockServletResponse, null); + // when + String actual = sut.format(mockServletRequest, mockServletResponse, null); - // then - assertThat(actual, containsString("[07/02/1991:01:02:03Z]")); - } + // then + assertThat(actual, containsString("[07/02/1991:01:02:03Z]")); + } - @Test - public void logsRequestTimeWhenRequestTimeEpochGreaterThanZero() { - // given - context.setRequestTimeEpoch(1563023494000L); + @Test + void logsRequestTimeWhenRequestTimeEpochGreaterThanZero() { + // given + context.setRequestTimeEpoch(1563023494000L); - // when - String actual = sut.format(mockServletRequest, mockServletResponse, null); + // when + String actual = sut.format(mockServletRequest, mockServletResponse, null); - // then - assertThat(actual, containsString("[13/07/2019:13:11:34Z]")); - } + // then + assertThat(actual, containsString("[13/07/2019:13:11:34Z]")); + } } 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 f379ff32a..a8383b5c3 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 @@ -10,19 +10,20 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.services.lambda.runtime.Context; -import org.junit.Test; - -import javax.servlet.AsyncContext; -import javax.servlet.Servlet; -import javax.servlet.ServletException; -import javax.servlet.ServletRegistration; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import jakarta.servlet.AsyncContext; +import jakarta.servlet.Servlet; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRegistration; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.concurrent.CountDownLatch; -import static junit.framework.TestCase.assertNotNull; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; public class AwsAsyncContextTest { private MockLambdaContext lambdaCtx = new MockLambdaContext(); @@ -32,48 +33,20 @@ public class AwsAsyncContextTest { private AwsServletContextTest.TestServlet srv2 = new AwsServletContextTest.TestServlet("srv2"); private AwsServletContext ctx = getCtx(); - @Test - public void dispatch_sendsToCorrectServlet() { - AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest(new AwsProxyRequestBuilder("/srv1/hello", "GET").build(), lambdaCtx, null); - req.setResponse(handler.getContainerResponse(req, new CountDownLatch(1))); - req.setServletContext(ctx); - req.setContainerHandler(handler); - - AsyncContext asyncCtx = req.startAsync(); - handler.setDesiredStatus(201); - asyncCtx.dispatch(); - assertNotNull(handler.getSelectedServlet()); - assertEquals(srv1, handler.getSelectedServlet()); - assertEquals(201, handler.getResponse().getStatus()); - - req = new AwsProxyHttpServletRequest(new AwsProxyRequestBuilder("/srv5/hello", "GET").build(), lambdaCtx, null); - req.setResponse(handler.getContainerResponse(req, new CountDownLatch(1))); - req.setServletContext(ctx); - req.setContainerHandler(handler); - asyncCtx = req.startAsync(); - handler.setDesiredStatus(202); - asyncCtx.dispatch(); - assertNotNull(handler.getSelectedServlet()); - assertEquals(srv2, handler.getSelectedServlet()); - assertEquals(202, handler.getResponse().getStatus()); - } @Test - public void dispatchNewPath_sendsToCorrectServlet() throws InvalidRequestEventException { - AwsProxyHttpServletRequest req = (AwsProxyHttpServletRequest) reader.readRequest(new AwsProxyRequestBuilder("/srv1/hello", "GET").build(), null, lambdaCtx, LambdaContainerHandler.getContainerConfig()); + void dispatch_amendsPath() 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); AsyncContext asyncCtx = req.startAsync(); - handler.setDesiredStatus(301); asyncCtx.dispatch("/srv4/hello"); - assertNotNull(handler.getSelectedServlet()); - assertEquals(srv2, handler.getSelectedServlet()); - assertNotNull(handler.getResponse()); - assertEquals(301, handler.getResponse().getStatus()); + assertEquals("/srv1/hello", req.getRequestURI()); } + private AwsServletContext getCtx() { AwsServletContext ctx = new AwsServletContext(handler); handler.setServletContext(ctx); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java index 8bc753540..1a51b5581 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java @@ -2,21 +2,24 @@ import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.internal.testutils.MockServlet; import com.amazonaws.services.lambda.runtime.Context; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.*; +import jakarta.servlet.*; import java.io.IOException; import java.util.EnumSet; import java.util.concurrent.CountDownLatch; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class AwsFilterChainManagerTest { + private static final String SERVLET1_NAME = "Servlet 1"; + private static final String SERVLET2_NAME = "Servlet 2"; private static final String REQUEST_CUSTOM_ATTRIBUTE_NAME = "X-Custom-Attribute"; private static final String REQUEST_CUSTOM_ATTRIBUTE_VALUE = "CustomAttrValue"; @@ -26,7 +29,7 @@ public class AwsFilterChainManagerTest { private Logger log = LoggerFactory.getLogger(AwsFilterChainManagerTest.class); - @BeforeClass + @BeforeAll public static void setUp() { servletContext = new AwsServletContext( null);//AwsServletContext.getInstance(lambdaContext, null); @@ -36,12 +39,16 @@ public static void setUp() { reg2.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/second/*"); FilterRegistration.Dynamic reg3 = servletContext.addFilter("Filter3", new MockFilter()); reg3.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/third/fourth/*"); + ServletRegistration.Dynamic firstServlet = servletContext.addServlet(SERVLET1_NAME, new MockServlet()); + firstServlet.addMapping("/first/*"); + ServletRegistration.Dynamic secondServlet = servletContext.addServlet(SERVLET2_NAME, new MockServlet()); + secondServlet.addMapping("/second/*"); chainManager = new AwsFilterChainManager((AwsServletContext) servletContext); } @Test - public void paths_pathMatches_validPaths() { + void paths_pathMatches_validPaths() { assertTrue(chainManager.pathMatches("/users/123123123", "/users/*")); assertTrue(chainManager.pathMatches("/apis/123/methods", "/apis/*")); assertTrue(chainManager.pathMatches("/very/long/path/with/sub/resources", "/*")); @@ -52,7 +59,7 @@ public void paths_pathMatches_validPaths() { } @Test - public void paths_pathMatches_invalidPaths() { + void paths_pathMatches_invalidPaths() { // I expect we'd want to run filters on these requests, especially the ones that look invalid assertTrue(chainManager.pathMatches("_%Garbled%20Path_%", "/*")); assertTrue(chainManager.pathMatches("", "/*")); @@ -61,7 +68,7 @@ public void paths_pathMatches_invalidPaths() { } @Test - public void cacheKey_compare_samePath() { + void cacheKey_compare_samePath() { FilterChainManager.TargetCacheKey cacheKey = new FilterChainManager.TargetCacheKey(); cacheKey.setDispatcherType(DispatcherType.REQUEST); cacheKey.setTargetPath("/first/path"); @@ -71,11 +78,11 @@ public void cacheKey_compare_samePath() { secondCacheKey.setTargetPath("/first/path"); assertEquals(cacheKey.hashCode(), secondCacheKey.hashCode()); - assertTrue(cacheKey.equals(secondCacheKey)); + assertEquals(cacheKey, secondCacheKey); } @Test - public void cacheKey_compare_differentDispatcher() { + void cacheKey_compare_differentDispatcher() { FilterChainManager.TargetCacheKey cacheKey = new FilterChainManager.TargetCacheKey(); cacheKey.setDispatcherType(DispatcherType.REQUEST); cacheKey.setTargetPath("/first/path"); @@ -85,11 +92,27 @@ public void cacheKey_compare_differentDispatcher() { secondCacheKey.setTargetPath("/first/path"); assertNotEquals(cacheKey.hashCode(), secondCacheKey.hashCode()); - assertFalse(cacheKey.equals(secondCacheKey)); + assertNotEquals(cacheKey, secondCacheKey); } @Test - public void cacheKey_compare_additionalChars() { + void cacheKey_compare_differentServlet() { + FilterChainManager.TargetCacheKey cacheKey = new FilterChainManager.TargetCacheKey(); + cacheKey.setDispatcherType(DispatcherType.REQUEST); + cacheKey.setTargetPath("/first/path"); + cacheKey.setServletName("Dispatcher servlet"); + + FilterChainManager.TargetCacheKey secondCacheKey = new FilterChainManager.TargetCacheKey(); + secondCacheKey.setDispatcherType(DispatcherType.REQUEST); + secondCacheKey.setTargetPath("/first/path"); + cacheKey.setServletName("Real servlet"); + + assertNotEquals(cacheKey.hashCode(), secondCacheKey.hashCode()); + assertNotEquals(cacheKey, secondCacheKey); + } + + @Test + void cacheKey_compare_additionalChars() { FilterChainManager.TargetCacheKey cacheKey = new FilterChainManager.TargetCacheKey(); cacheKey.setDispatcherType(DispatcherType.REQUEST); cacheKey.setTargetPath("/first/path"); @@ -98,21 +121,21 @@ public void cacheKey_compare_additionalChars() { secondCacheKey.setDispatcherType(DispatcherType.REQUEST); secondCacheKey.setTargetPath("/first/path/"); assertEquals(cacheKey.hashCode(), secondCacheKey.hashCode()); - assertTrue(cacheKey.equals(secondCacheKey)); + assertEquals(cacheKey, secondCacheKey); secondCacheKey.setTargetPath(" /first/path"); assertEquals(cacheKey.hashCode(), secondCacheKey.hashCode()); - assertTrue(cacheKey.equals(secondCacheKey)); + assertEquals(cacheKey, secondCacheKey); secondCacheKey.setTargetPath("first/path/"); assertEquals(cacheKey.hashCode(), secondCacheKey.hashCode()); - assertTrue(cacheKey.equals(secondCacheKey)); + assertEquals(cacheKey, secondCacheKey); } @Test - public void filterChain_getFilterChain_subsetOfFilters() { + void filterChain_getFilterChain_subsetOfFilters() { AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest( - new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null + new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null ); req.setServletContext(servletContext); FilterChainHolder fcHolder = chainManager.getFilterChain(req, null); @@ -135,9 +158,9 @@ public void filterChain_getFilterChain_subsetOfFilters() { } @Test - public void filterChain_matchMultipleTimes_expectSameMatch() { + void filterChain_matchMultipleTimes_expectSameMatch() { AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest( - new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null + new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null ); req.setServletContext(servletContext); FilterChainHolder fcHolder = chainManager.getFilterChain(req, null); @@ -145,7 +168,7 @@ public void filterChain_matchMultipleTimes_expectSameMatch() { assertEquals("Filter1", fcHolder.getFilter(0).getFilterName()); AwsProxyHttpServletRequest req2 = new AwsProxyHttpServletRequest( - new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null + new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null ); req.setServletContext(servletContext); FilterChainHolder fcHolder2 = chainManager.getFilterChain(req2, null); @@ -154,9 +177,9 @@ public void filterChain_matchMultipleTimes_expectSameMatch() { } @Test - public void filerChain_executeMultipleFilters_expectRunEachTime() { + void filterChain_executeMultipleFilters_expectRunEachTime() { AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest( - new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null + new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null ); req.setServletContext(servletContext); FilterChainHolder fcHolder = chainManager.getFilterChain(req, null); @@ -180,7 +203,7 @@ public void filerChain_executeMultipleFilters_expectRunEachTime() { log.debug("Starting second request"); AwsProxyHttpServletRequest req2 = new AwsProxyHttpServletRequest( - new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null + new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null ); req2.setServletContext(servletContext); FilterChainHolder fcHolder2 = chainManager.getFilterChain(req2, null); @@ -205,7 +228,35 @@ public void filerChain_executeMultipleFilters_expectRunEachTime() { } @Test - public void filterChain_getFilterChain_multipleFilters() { + void filterChain_multipleServlets_callsCorrectServlet() throws IOException, ServletException { + MockServlet servlet1 = (MockServlet) ((AwsServletRegistration) servletContext.getServletRegistration(SERVLET1_NAME)).getServlet(); + ServletConfig servlet1Config = ((AwsServletRegistration) servletContext.getServletRegistration(SERVLET1_NAME)).getServletConfig(); + servlet1.init(servlet1Config); + + MockServlet servlet2 = (MockServlet) ((AwsServletRegistration) servletContext.getServletRegistration(SERVLET2_NAME)).getServlet(); + ServletConfig servlet2Config = ((AwsServletRegistration) servletContext.getServletRegistration(SERVLET2_NAME)).getServletConfig(); + servlet2.init(servlet2Config); + + AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest( + new AwsProxyRequestBuilder("/", "GET").build(), lambdaContext, null + ); + AwsHttpServletResponse resp = new AwsHttpServletResponse(req, new CountDownLatch(1)); + + FilterChainHolder servlet1filterChain = chainManager.getFilterChain(req, servlet1); + servlet1filterChain.doFilter(req, resp); + + assertEquals(1, servlet1.getServiceCalls()); + assertEquals(0, servlet2.getServiceCalls()); + + FilterChainHolder servlet2filterChain = chainManager.getFilterChain(req, servlet2); + servlet2filterChain.doFilter(req, resp); + + assertEquals(1, servlet1.getServiceCalls()); + assertEquals(1, servlet2.getServiceCalls()); + } + + @Test + void filterChain_getFilterChain_multipleFilters() { AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest( new AwsProxyRequestBuilder("/second/important", "GET").build(), lambdaContext, null ); 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 index 37ba30598..12f694bc1 100644 --- 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 @@ -5,23 +5,23 @@ 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 org.junit.jupiter.api.Test; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.core.HttpHeaders; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.HttpHeaders; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class AwsHttpApiV2HttpServletRequestReaderTest { private AwsHttpApiV2HttpServletRequestReader reader = new AwsHttpApiV2HttpServletRequestReader(); @Test - public void reflection_getRequestClass_returnsCorrectType() { + void reflection_getRequestClass_returnsCorrectType() { assertSame(HttpApiV2ProxyRequest.class, reader.getRequestClass()); } @Test - public void baseRequest_read_populatesSuccessfully() { + void baseRequest_read_populatesSuccessfully() { HttpApiV2ProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET") .referer("localhost") .userAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36") diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java index 5372dfa95..83c747243 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java @@ -1,22 +1,22 @@ package com.amazonaws.serverless.proxy.internal.servlet; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; 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.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import javax.servlet.ServletException; -import javax.servlet.http.Cookie; -import javax.ws.rs.core.HttpHeaders; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.ws.rs.core.HttpHeaders; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import java.util.Base64; import java.util.List; +import java.util.Map; public class AwsHttpServletRequestTest { @@ -25,23 +25,41 @@ public class AwsHttpServletRequestTest { .header(HttpHeaders.CONTENT_TYPE, "application/xml; charset=utf-8").build(); private static final AwsProxyRequest validCookieRequest = new AwsProxyRequestBuilder("/cookie", "GET") .header(HttpHeaders.COOKIE, "yummy_cookie=choco; tasty_cookie=strawberry").build(); + private static final AwsProxyRequest controlCharCookieRequest = new AwsProxyRequestBuilder("/cookie", "GET") + .header(HttpHeaders.COOKIE, "name=\u0007\u0009; tasty_cookie=strawberry").build(); + private static final AwsProxyRequest unicodeCookieRequest = new AwsProxyRequestBuilder("/cookie", "GET") + .header(HttpHeaders.COOKIE, "yummy_cookie=chøcø; tasty_cookie=strawberry").build(); + private static final AwsProxyRequest invalidNameCookieRequest = new AwsProxyRequestBuilder("/cookie", "GET") + .header(HttpHeaders.COOKIE, "yummy@cookie=choco; tasty_cookie=strawberry").build(); private static final AwsProxyRequest complexAcceptHeader = new AwsProxyRequestBuilder("/accept", "GET") .header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8").build(); private static final AwsProxyRequest queryString = new AwsProxyRequestBuilder("/test", "GET") .queryString("one", "two").queryString("three", "four").build(); private static final AwsProxyRequest queryStringNullValue = new AwsProxyRequestBuilder("/test", "GET") .queryString("one", "two").queryString("three", null).build(); + private static final AwsProxyRequest queryStringEmptyValue = new AwsProxyRequestBuilder("/test", "GET") + .queryString("one", "two").queryString("three", "").build(); private static final AwsProxyRequest encodedQueryString = new AwsProxyRequestBuilder("/test", "GET") - .queryString("one", "two").queryString("json", "{\"name\":\"faisal\"}").build(); + .queryString("one", "two").queryString("json value@1", "{\"name\":\"faisal\"}").build(); + private static final AwsProxyRequest encodedQueryStringAlb = new AwsProxyRequestBuilder("/test", "GET") + .queryString("one", "two").queryString("json value@1", "{\"name\":\"faisal\"}").alb().build(); private static final AwsProxyRequest multipleParams = new AwsProxyRequestBuilder("/test", "GET") - .queryString("one", "two").queryString("one", "three").queryString("json", "{\"name\":\"faisal\"}").build(); + .queryString("one", "two").queryString("one", "three").queryString("json value@1", "{\"name\":\"faisal\"}").build(); + private static final AwsProxyRequest formEncodedAndQueryString = new AwsProxyRequestBuilder("/test", "POST") + .queryString("one", "two").queryString("one", "three") + .queryString("five", "six") + .form("one", "four") + .form("seven", "eight").build(); + private static final AwsProxyRequest differentCasing = new AwsProxyRequestBuilder("/test", "POST") + .queryString("one", "two").queryString("one", "three") + .queryString("ONE", "four").build(); private static final MockLambdaContext mockContext = new MockLambdaContext(); private static ContainerConfig config = ContainerConfig.defaultConfig(); @Test - public void headers_parseHeaderValue_multiValue() { + void headers_parseHeaderValue_multiValue() { AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(contentTypeRequest, mockContext, null, config); // I'm also using this to double-check that I can get a header ignoring case List values = request.parseHeaderValue(request.getHeader("content-type")); @@ -55,7 +73,7 @@ public void headers_parseHeaderValue_multiValue() { } @Test - public void headers_parseHeaderValue_validMultipleCookie() { + void headers_parseHeaderValue_validMultipleCookie() { AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(validCookieRequest, mockContext, null, config); List values = request.parseHeaderValue(request.getHeader(HttpHeaders.COOKIE), ";", ","); @@ -67,7 +85,40 @@ public void headers_parseHeaderValue_validMultipleCookie() { } @Test - public void headers_parseHeaderValue_complexAccept() { + void headers_parseHeaderValue_controlCharCookie() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(controlCharCookieRequest, mockContext, null, config); + Cookie[] cookies = request.getCookies(); + + // parse only valid cookies + assertEquals(1, cookies.length); + assertEquals("tasty_cookie", cookies[0].getName()); + assertEquals("strawberry", cookies[0].getValue()); + } + + @Test + void headers_parseHeaderValue_unicodeCookie() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(unicodeCookieRequest, mockContext, null, config); + Cookie[] cookies = request.getCookies(); + + // parse only valid cookies + assertEquals(1, cookies.length); + assertEquals("tasty_cookie", cookies[0].getName()); + assertEquals("strawberry", cookies[0].getValue()); + } + + @Test + void headers_parseHeaderValue_invalidNameCookie() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(invalidNameCookieRequest, mockContext, null, config); + Cookie[] cookies = request.getCookies(); + + // parse only valid cookies + assertEquals(1, cookies.length); + assertEquals("tasty_cookie", cookies[0].getName()); + assertEquals("strawberry", cookies[0].getValue()); + } + + @Test + void headers_parseHeaderValue_complexAccept() { AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(complexAcceptHeader, mockContext, null, config); List values = request.parseHeaderValue(request.getHeader(HttpHeaders.ACCEPT), ",", ";"); @@ -75,8 +126,8 @@ public void headers_parseHeaderValue_complexAccept() { } @Test - public void headers_parseHeaderValue_encodedContentWithEquals() { - AwsHttpServletRequest context = new AwsProxyHttpServletRequest(null,null,null); + void headers_parseHeaderValue_encodedContentWithEquals() { + AwsHttpServletRequest context = new AwsProxyHttpServletRequest(null, null, null); String value = Base64.getUrlEncoder().encodeToString("a".getBytes()); @@ -86,11 +137,11 @@ public void headers_parseHeaderValue_encodedContentWithEquals() { } @Test - public void headers_parseHeaderValue_base64EncodedCookieValue() { + void headers_parseHeaderValue_base64EncodedCookieValue() { String value = Base64.getUrlEncoder().encodeToString("a".getBytes()); String cookieValue = "jwt=" + value + "; secondValue=second"; AwsProxyRequest req = new AwsProxyRequestBuilder("/test", "GET").header(HttpHeaders.COOKIE, cookieValue).build(); - AwsHttpServletRequest context = new AwsProxyHttpServletRequest(req,null,null); + AwsHttpServletRequest context = new AwsProxyHttpServletRequest(req, null, null); Cookie[] cookies = context.getCookies(); @@ -100,10 +151,10 @@ public void headers_parseHeaderValue_base64EncodedCookieValue() { } @Test - public void headers_parseHeaderValue_cookieWithSeparatorInValue() { + void headers_parseHeaderValue_cookieWithSeparatorInValue() { String cookieValue = "jwt==test; secondValue=second"; AwsProxyRequest req = new AwsProxyRequestBuilder("/test", "GET").header(HttpHeaders.COOKIE, cookieValue).build(); - AwsHttpServletRequest context = new AwsProxyHttpServletRequest(req,null,null); + AwsHttpServletRequest context = new AwsProxyHttpServletRequest(req, null, null); Cookie[] cookies = context.getCookies(); @@ -113,8 +164,8 @@ public void headers_parseHeaderValue_cookieWithSeparatorInValue() { } @Test - public void headers_parseHeaderValue_headerWithPaddingButNotBase64Encoded() { - AwsHttpServletRequest context = new AwsProxyHttpServletRequest(null,null,null); + void headers_parseHeaderValue_headerWithPaddingButNotBase64Encoded() { + AwsHttpServletRequest context = new AwsProxyHttpServletRequest(null, null, null); List result = context.parseHeaderValue("hello="); assertTrue(result.size() > 0); @@ -123,7 +174,7 @@ public void headers_parseHeaderValue_headerWithPaddingButNotBase64Encoded() { } @Test - public void queryString_generateQueryString_validQuery() { + void queryString_generateQueryString_validQuery() { AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryString, mockContext, null, config); String parsedString = null; @@ -139,8 +190,9 @@ public void queryString_generateQueryString_validQuery() { } @Test - public void queryString_generateQueryString_nullParameterIsEmpty() { - AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryStringNullValue, mockContext, null, config);String parsedString = null; + void queryString_generateQueryString_nullParameterIsEmpty() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryStringNullValue, mockContext, null, config); + String parsedString = null; try { parsedString = request.generateQueryString(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), true, config.getUriEncoding()); } catch (ServletException e) { @@ -152,7 +204,21 @@ public void queryString_generateQueryString_nullParameterIsEmpty() { } @Test - public void queryStringWithEncodedParams_generateQueryString_validQuery() { + void queryString_generateQueryString_emptyParameterIsEmpty() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryStringEmptyValue, mockContext, null, config); + String parsedString = null; + try { + parsedString = request.generateQueryString(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), true, config.getUriEncoding()); + } catch (ServletException e) { + e.printStackTrace(); + fail("Could not generate query string"); + } + + assertTrue(parsedString.endsWith("three=")); + } + + @Test + void queryStringWithEncodedParams_generateQueryString_validQuery() { AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(encodedQueryString, mockContext, null, config); String parsedString = null; @@ -163,12 +229,28 @@ public void queryStringWithEncodedParams_generateQueryString_validQuery() { fail("Could not generate query string"); } assertTrue(parsedString.contains("one=two")); - assertTrue(parsedString.contains("json=%7B%22name%22%3A%22faisal%22%7D")); + assertTrue(parsedString.contains("json+value%401=%7B%22name%22%3A%22faisal%22%7D")); + assertTrue(parsedString.contains("&") && parsedString.indexOf("&") > 0 && parsedString.indexOf("&") < parsedString.length()); + } + + @Test + void queryStringWithEncodedParams_alb_generateQueryString_validQuery() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(encodedQueryStringAlb, mockContext, null, config); + + String parsedString = null; + try { + parsedString = request.generateQueryString(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), false, config.getUriEncoding()); + } catch (ServletException e) { + e.printStackTrace(); + fail("Could not generate query string"); + } + assertTrue(parsedString.contains("one=two")); + assertTrue(parsedString.contains("json+value%401=%7B%22name%22%3A%22faisal%22%7D")); assertTrue(parsedString.contains("&") && parsedString.indexOf("&") > 0 && parsedString.indexOf("&") < parsedString.length()); } @Test - public void queryStringWithMultipleValues_generateQueryString_validQuery() { + void queryStringWithMultipleValues_generateQueryString_validQuery() { AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(multipleParams, mockContext, null, config); String parsedString = null; @@ -180,7 +262,210 @@ public void queryStringWithMultipleValues_generateQueryString_validQuery() { } assertTrue(parsedString.contains("one=two")); assertTrue(parsedString.contains("one=three")); - assertTrue(parsedString.contains("json=%7B%22name%22%3A%22faisal%22%7D")); + assertTrue(parsedString.contains("json+value%401=%7B%22name%22%3A%22faisal%22%7D")); assertTrue(parsedString.contains("&") && parsedString.indexOf("&") > 0 && parsedString.indexOf("&") < parsedString.length()); } + + @Test + void parameterMap_generateParameterMap_validQuery() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryString, mockContext, null, config); + + Map paramMap = null; + try { + paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config); + } catch (Exception e) { + e.printStackTrace(); + fail("Could not generate parameter map"); + } + assertArrayEquals(new String[]{"two"}, paramMap.get("one")); + assertArrayEquals(new String[]{"four"}, paramMap.get("three")); + assertTrue(paramMap.size() == 2); + } + + @Test + void parameterMap_generateParameterMap_nullParameter() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryStringNullValue, mockContext, null, config); + Map paramMap = null; + try { + paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config); + } catch (Exception e) { + e.printStackTrace(); + fail("Could not generate parameter map"); + } + + assertArrayEquals(new String[]{"two"}, paramMap.get("one")); + assertArrayEquals(new String[]{null}, paramMap.get("three")); + assertTrue(paramMap.size() == 2); + } + + @Test + void parameterMap_generateParameterMap_emptyParameter() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryStringEmptyValue, mockContext, null, config); + Map paramMap = null; + try { + paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config); + } catch (Exception e) { + e.printStackTrace(); + fail("Could not generate parameter map"); + } + + assertArrayEquals(new String[]{"two"}, paramMap.get("one")); + assertArrayEquals(new String[]{""}, paramMap.get("three")); + assertTrue(paramMap.size() == 2); + } + + @Test + void parameterMapWithEncodedParams_generateParameterMap_validQuery() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(encodedQueryString, mockContext, null, config); + + Map paramMap = null; + try { + paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config); + } catch (Exception e) { + e.printStackTrace(); + fail("Could not generate parameter map"); + } + + assertArrayEquals(new String[]{"two"}, paramMap.get("one")); + assertArrayEquals(new String[]{"{\"name\":\"faisal\"}"}, paramMap.get("json value@1")); + assertTrue(paramMap.size() == 2); + } + + @Test + void parameterMapWithEncodedParams_alb_generateParameterMap_validQuery() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(encodedQueryStringAlb, mockContext, null, config); + + Map paramMap = null; + try { + paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config, true); + } catch (Exception e) { + e.printStackTrace(); + fail("Could not generate parameter map"); + } + + assertArrayEquals(new String[]{"two"}, paramMap.get("one")); + assertArrayEquals(new String[]{"{\"name\":\"faisal\"}"}, paramMap.get("json value@1")); + assertTrue(paramMap.size() == 2); + } + + @Test + void parameterMapWithMultipleValues_generateParameterMap_validQuery() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(multipleParams, mockContext, null, config); + + Map paramMap = null; + try { + paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config); + } catch (Exception e) { + e.printStackTrace(); + fail("Could not generate parameter map"); + } + assertArrayEquals(new String[]{"two", "three"}, paramMap.get("one")); + assertArrayEquals(new String[]{"{\"name\":\"faisal\"}"}, paramMap.get("json value@1")); + assertTrue(paramMap.size() == 2); + } + + @Test + void parameterMap_generateParameterMap_formEncodedAndQueryString() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(formEncodedAndQueryString, mockContext, null, config); + + Map paramMap = null; + try { + paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config); + } catch (Exception e) { + e.printStackTrace(); + fail("Could not generate parameter map"); + } + // Combines form encoded parameters (one=four) with query string (one=two,three) + // The order between them is not officially guaranteed (it could be four,two,three or two,three,four) + // Current implementation gives form encoded parameters first + assertArrayEquals(new String[]{"four", "two", "three"}, paramMap.get("one")); + assertArrayEquals(new String[]{"six"}, paramMap.get("five")); + assertArrayEquals(new String[]{"eight"}, paramMap.get("seven")); + assertTrue(paramMap.size() == 3); + } + + @Test + void parameterMap_generateParameterMap_differentCasing_caseSensitive() { + ContainerConfig caseSensitiveConfig = ContainerConfig.defaultConfig(); + caseSensitiveConfig.setQueryStringCaseSensitive(true); + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(differentCasing, mockContext, null, caseSensitiveConfig); + Map paramMap = null; + try { + paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), caseSensitiveConfig); + } catch (Exception e) { + e.printStackTrace(); + fail("Could not generate parameter map"); + } + assertArrayEquals(new String[] {"two", "three"}, paramMap.get("one")); + assertArrayEquals(new String[] {"four"}, paramMap.get("ONE")); + assertTrue(paramMap.size() == 2); + } + + @Test + void parameterMap_generateParameterMap_differentCasing_caseInsensitive() { + ContainerConfig caseInsensitiveConfig = ContainerConfig.defaultConfig(); + caseInsensitiveConfig.setQueryStringCaseSensitive(false); + + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(differentCasing, mockContext, null, caseInsensitiveConfig); + + Map paramMap = null; + try { + paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), caseInsensitiveConfig); + } catch (Exception e) { + e.printStackTrace(); + fail("Could not generate parameter map"); + } + // If a parameter is duplicated but with a different casing, it's replaced with only one of them + assertArrayEquals(paramMap.get("one"), paramMap.get("ONE")); + assertTrue(paramMap.size() == 2); + } + + @Test + void queryParamValues_getQueryParamValues() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(new AwsProxyRequest(), mockContext, null); + MultiValuedTreeMap map = new MultiValuedTreeMap<>(); + map.add("test", "test"); + map.add("test", "test2"); + String[] result1 = request.getQueryParamValues(map, "test", true); + assertArrayEquals(new String[]{"test", "test2"}, result1); + String[] result2 = request.getQueryParamValues(map, "TEST", true); + assertNull(result2); + } + + @Test + void queryParamValues_getQueryParamValues_nullValue() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(new AwsProxyRequest(), mockContext, null); + MultiValuedTreeMap map = new MultiValuedTreeMap<>(); + map.add("test", null); + String[] result1 = request.getQueryParamValues(map, "test", true); + assertArrayEquals(new String[] {null}, result1); + String[] result2 = request.getQueryParamValues(map, "TEST", true); + assertNull(result2); + } + + @Test + void queryParamValues_getQueryParamValues_caseInsensitive() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(new AwsProxyRequest(), mockContext, null); + MultiValuedTreeMap map = new MultiValuedTreeMap<>(); + map.add("test", "test"); + map.add("test", "test2"); + String[] result1 = request.getQueryParamValues(map, "test", false); + assertArrayEquals(new String[]{"test", "test2"}, result1); + String[] result2 = request.getQueryParamValues(map, "TEST", false); + assertArrayEquals(new String[]{"test", "test2"}, result2); + } + + @Test + void queryParamValues_getQueryParamValues_multipleCaseInsensitive() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(new AwsProxyRequest(), mockContext, null); + + MultiValuedTreeMap map = new MultiValuedTreeMap<>(); + map.add("test", "test"); + map.add("TEST", "test2"); + String[] result1 = request.getQueryParamValues(map, "test", false); + assertArrayEquals(new String[]{"test2"}, result1); + String[] result2 = request.getQueryParamValues(map, "TEST", false); + assertArrayEquals(new String[]{"test2"}, result2); + } + } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java index 4731a0359..143caab69 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java @@ -4,24 +4,26 @@ import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.serverless.proxy.model.Headers; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import javax.servlet.http.Cookie; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; +import jakarta.servlet.http.Cookie; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; import java.io.IOException; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; import java.util.Calendar; +import java.util.Locale; import java.util.TimeZone; import java.util.concurrent.CountDownLatch; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class AwsHttpServletResponseTest { @@ -35,12 +37,13 @@ public class AwsHttpServletResponseTest { private static final int MAX_AGE_VALUE = 300; private static final Pattern MAX_AGE_PATTERN = Pattern.compile("Max-Age=(-?[0-9]+)"); - private static final Pattern EXPIRES_PATTERN = Pattern.compile("Expires=(.*)$"); + private static final Pattern EXPIRES_PATTERN = Pattern.compile("Expires=([^;]+)"); private static final String CONTENT_TYPE_WITH_CHARSET = "application/json; charset=UTF-8"; + private static final String JAVASCRIPT_CONTENT_TYPE_WITH_CHARSET = "application/javascript; charset=UTF-8"; @Test - public void cookie_addCookie_verifyPath() { + void cookie_addCookie_verifyPath() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); Cookie pathCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); pathCookie.setPath(COOKIE_PATH); @@ -53,7 +56,7 @@ public void cookie_addCookie_verifyPath() { } @Test - public void cookie_addCookie_verifySecure() { + void cookie_addCookie_verifySecure() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); Cookie secureCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); secureCookie.setSecure(true); @@ -66,7 +69,7 @@ public void cookie_addCookie_verifySecure() { } @Test - public void cookie_addCookie_verifyDomain() { + void cookie_addCookie_verifyDomain() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); Cookie domainCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); domainCookie.setDomain(COOKIE_DOMAIN); @@ -79,7 +82,7 @@ public void cookie_addCookie_verifyDomain() { } @Test - public void cookie_addCookie_defaultMaxAgeIsNegative() { + void cookie_addCookie_defaultMaxAgeIsNegative() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); Cookie maxAgeCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); maxAgeCookie.setDomain(COOKIE_DOMAIN); @@ -92,7 +95,7 @@ public void cookie_addCookie_defaultMaxAgeIsNegative() { } @Test - public void cookie_addCookie_positiveMaxAgeIsPresent() { + void cookie_addCookie_positiveMaxAgeIsPresent() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); Cookie maxAgeCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); maxAgeCookie.setMaxAge(MAX_AGE_VALUE); @@ -108,7 +111,7 @@ public void cookie_addCookie_positiveMaxAgeIsPresent() { } @Test - public void cookie_addCookie_positiveMaxAgeExpiresDate() { + void cookie_addCookie_positiveMaxAgeExpiresDate() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); Cookie maxAgeCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); maxAgeCookie.setMaxAge(MAX_AGE_VALUE); @@ -132,7 +135,7 @@ public void cookie_addCookie_positiveMaxAgeExpiresDate() { } @Test - public void cookie_addCookieWithoutMaxAge_expectNoExpires() { + void cookie_addCookieWithoutMaxAge_expectNoExpires() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); Cookie simpleCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); resp.addCookie(simpleCookie); @@ -143,7 +146,24 @@ public void cookie_addCookieWithoutMaxAge_expectNoExpires() { } @Test - public void responseHeaders_getAwsResponseHeaders_expectLatestHeader() { + void cookie_addCookieWithMaxAgeZero_expectExpiresInThePast() { + AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); + Cookie zeroMaxAgeCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); + zeroMaxAgeCookie.setMaxAge(0); + + resp.addCookie(zeroMaxAgeCookie); + String cookieHeader = resp.getHeader(HttpHeaders.SET_COOKIE); + + Calendar cal = getExpires(cookieHeader); + long currentTimeMillis = System.currentTimeMillis(); + + assertNotNull(cookieHeader); + assertTrue(cal.getTimeInMillis() < currentTimeMillis); + assertTrue(cookieHeader.contains(COOKIE_NAME + "=" + COOKIE_VALUE)); + } + + @Test + void responseHeaders_getAwsResponseHeaders_expectLatestHeader() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); resp.addHeader("content-type", "application/xml"); @@ -154,7 +174,17 @@ public void responseHeaders_getAwsResponseHeaders_expectLatestHeader() { } @Test - public void responseHeaders_getAwsResponseHeaders_expectedMultpleCookieHeaders() { + void responseHeaders_setHeaderWithNullValue_expectHeaderRemoved() { + AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); + resp.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline"); + resp.setHeader(HttpHeaders.CONTENT_DISPOSITION, null); + + Headers awsResp = resp.getAwsResponseHeaders(); + assertEquals(0, awsResp.size()); + } + + @Test + void responseHeaders_getAwsResponseHeaders_expectedMultpleCookieHeaders() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.addCookie(new Cookie(COOKIE_NAME, COOKIE_VALUE)); resp.addCookie(new Cookie("Second", "test")); @@ -165,7 +195,7 @@ public void responseHeaders_getAwsResponseHeaders_expectedMultpleCookieHeaders() } @Test - public void releaseLatch_flushBuffer_expectFlushToWriteAndRelease() { + void releaseLatch_flushBuffer_expectFlushToWriteAndRelease() { CountDownLatch respLatch = new CountDownLatch(1); AwsHttpServletResponse resp = new AwsHttpServletResponse(null, respLatch); String respBody = "Test resp"; @@ -200,7 +230,7 @@ public void releaseLatch_flushBuffer_expectFlushToWriteAndRelease() { } @Test - public void dateHeader_addDateHeader_expectMultipleHeaders() { + void dateHeader_addDateHeader_expectMultipleHeaders() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.addDateHeader("Date", Instant.now().toEpochMilli()); resp.addDateHeader("Date", Instant.now().toEpochMilli() - 1000); @@ -209,7 +239,7 @@ public void dateHeader_addDateHeader_expectMultipleHeaders() { } @Test - public void dateHeader_setDateHeader_expectSingleHeader() { + void dateHeader_setDateHeader_expectSingleHeader() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.setDateHeader("Date", Instant.now().toEpochMilli()); resp.setDateHeader("Date", Instant.now().toEpochMilli() - 1000); @@ -218,7 +248,7 @@ public void dateHeader_setDateHeader_expectSingleHeader() { } @Test - public void response_reset_expectEmptyHeadersAndBody() { + void response_reset_expectEmptyHeadersAndBody() { CountDownLatch respLatch = new CountDownLatch(1); AwsHttpServletResponse resp = new AwsHttpServletResponse(null, respLatch); String body = "My Body"; @@ -240,7 +270,7 @@ public void response_reset_expectEmptyHeadersAndBody() { } @Test - public void headers_setIntHeader_expectSingleHeaderValue() { + void headers_setIntHeader_expectSingleHeaderValue() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.setIntHeader("Test", 15); resp.setIntHeader("Test", 34); @@ -251,7 +281,7 @@ public void headers_setIntHeader_expectSingleHeaderValue() { } @Test - public void headers_addIntHeader_expectMultipleHeaderValues() { + void headers_addIntHeader_expectMultipleHeaderValues() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.addIntHeader("Test", 15); resp.addIntHeader("Test", 34); @@ -262,7 +292,7 @@ public void headers_addIntHeader_expectMultipleHeaderValues() { } @Test - public void characterEncoding_setCharacterEncoding() { + void characterEncoding_setCharacterEncoding() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.setContentType("application/json"); resp.setCharacterEncoding("UTF-8"); @@ -272,7 +302,7 @@ public void characterEncoding_setCharacterEncoding() { } @Test - public void characterEncoding_setContentType() { + void characterEncoding_setContentType() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.setContentType("application/json; charset=utf-8"); resp.setCharacterEncoding("UTF-8"); @@ -283,7 +313,7 @@ public void characterEncoding_setContentType() { } @Test - public void characterEncoding_setContentTypeAndsetCharacterEncoding() { + void characterEncoding_setContentTypeAndsetCharacterEncoding() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.setContentType("application/json"); resp.setCharacterEncoding("UTF-8"); @@ -294,7 +324,7 @@ public void characterEncoding_setContentTypeAndsetCharacterEncoding() { } @Test - public void characterEncoding_setCharacterEncodingAndsetContentType() { + void characterEncoding_setCharacterEncodingAndsetContentType() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.setCharacterEncoding("UTF-8"); resp.setContentType("application/json"); @@ -305,7 +335,7 @@ public void characterEncoding_setCharacterEncodingAndsetContentType() { } @Test - public void characterEncoding_setCharacterEncodingInContentType_characterEncodingPopulatedCorrectly() { + void characterEncoding_setCharacterEncodingInContentType_characterEncodingPopulatedCorrectly() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.setContentType(CONTENT_TYPE_WITH_CHARSET); @@ -315,7 +345,7 @@ public void characterEncoding_setCharacterEncodingInContentType_characterEncodin } @Test - public void characterEncoding_setCharacterEncodingInContentType_overridesDefault() { + void characterEncoding_setCharacterEncodingInContentType_overridesDefault() { AwsHttpServletResponse resp = new AwsHttpServletResponse(null, null); resp.setCharacterEncoding(ContainerConfig.DEFAULT_CONTENT_CHARSET); resp.setContentType(CONTENT_TYPE_WITH_CHARSET); @@ -325,6 +355,18 @@ public void characterEncoding_setCharacterEncodingInContentType_overridesDefault assertEquals("UTF-8", resp.getCharacterEncoding()); } + @Test + void characterEncoding_encodingInContentTypeHeader_writesCorrectData() throws IOException { + AwsHttpServletResponse resp = new AwsHttpServletResponse(null, new CountDownLatch(1)); + resp.setHeader("Content-Type", JAVASCRIPT_CONTENT_TYPE_WITH_CHARSET); + resp.getOutputStream().write("ü".getBytes(StandardCharsets.UTF_8)); + resp.flushBuffer(); + + assertEquals(JAVASCRIPT_CONTENT_TYPE_WITH_CHARSET, resp.getContentType()); + assertEquals(JAVASCRIPT_CONTENT_TYPE_WITH_CHARSET, resp.getHeader("Content-Type")); + assertEquals("ü",resp.getAwsResponseBodyString()); + } + private int getMaxAge(String header) { Matcher ageMatcher = MAX_AGE_PATTERN.matcher(header); assertTrue(ageMatcher.find()); @@ -338,7 +380,7 @@ private Calendar getExpires(String header) { assertTrue(ageMatcher.find()); assertTrue(ageMatcher.groupCount() >= 1); String expiresString = ageMatcher.group(1); - SimpleDateFormat sdf = new SimpleDateFormat(AwsHttpServletResponse.HEADER_DATE_PATTERN); + SimpleDateFormat sdf = new SimpleDateFormat(AwsHttpServletResponse.HEADER_DATE_PATTERN, Locale.US); Calendar cal = Calendar.getInstance(); try { cal.setTime(sdf.parse(expiresString)); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSessionTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSessionTest.java index bb9e0d476..bcca83dda 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSessionTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSessionTest.java @@ -1,18 +1,16 @@ package com.amazonaws.serverless.proxy.internal.servlet; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.time.Instant; import java.util.Enumeration; -import static junit.framework.TestCase.assertTrue; -import static junit.framework.TestCase.fail; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class AwsHttpSessionTest { @Test - public void new_withNullId_throwsException() { + void new_withNullId_throwsException() { try { AwsHttpSession session = new AwsHttpSession(null); } catch (RuntimeException e) { @@ -23,13 +21,13 @@ public void new_withNullId_throwsException() { } @Test - public void new_withValidId_setsIdCorrectly() { + void new_withValidId_setsIdCorrectly() { AwsHttpSession session = new AwsHttpSession("id"); assertEquals("id", session.getId()); } @Test - public void new_creationTimePopulatedCorrectly() { + void new_creationTimePopulatedCorrectly() { AwsHttpSession session = new AwsHttpSession("id"); assertTrue(session.getCreationTime() > Instant.now().getEpochSecond() - 1); assertEquals(AwsHttpSession.SESSION_DURATION_SEC, session.getMaxInactiveInterval()); @@ -37,35 +35,7 @@ public void new_creationTimePopulatedCorrectly() { } @Test - public void values_throwsUnsupportedOperationException() { - int exCount = 0; - AwsHttpSession sess = new AwsHttpSession("id"); - - try { - sess.putValue("test", "test"); - } catch (UnsupportedOperationException e) { - exCount++; - } - try { - sess.removeValue("test"); - } catch (UnsupportedOperationException e) { - exCount++; - } - try { - sess.getValue("test"); - } catch (UnsupportedOperationException e) { - exCount++; - } - try { - sess.getValueNames(); - } catch (UnsupportedOperationException e) { - exCount++; - } - assertEquals(4, exCount); - } - - @Test - public void attributes_dataStoredCorrectly() throws InterruptedException { + void attributes_dataStoredCorrectly() throws InterruptedException { AwsHttpSession sess = new AwsHttpSession("id"); sess.setAttribute("test", "test"); sess.setAttribute("test2", "test2"); @@ -76,7 +46,7 @@ public void attributes_dataStoredCorrectly() throws InterruptedException { attrsCnt++; } assertEquals(2, attrsCnt); - assertEquals(sess.getAttribute("test"), "test"); + assertEquals("test", sess.getAttribute("test")); sess.removeAttribute("test2"); attrs = sess.getAttributeNames(); attrsCnt = 0; @@ -94,7 +64,7 @@ public void attributes_dataStoredCorrectly() throws InterruptedException { } @Test - public void validSession_expectCorrectValidationOrInvalidation() throws InterruptedException { + void validSession_expectCorrectValidationOrInvalidation() throws InterruptedException { AwsHttpSession sess = new AwsHttpSession("id"); assertTrue(sess.isValid()); assertTrue(sess.isNew()); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java index 838ea6607..67b147620 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java @@ -4,28 +4,24 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import jakarta.servlet.http.Part; import org.apache.commons.io.IOUtils; -import org.apache.http.HttpEntity; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.junit.Test; +import org.apache.hc.client5.http.entity.mime.MultipartPartBuilder; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.junit.jupiter.api.Test; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; import java.io.IOException; import java.nio.charset.Charset; -import java.util.Base64; -import java.util.Collections; -import java.util.Map; -import java.util.Random; +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.jupiter.api.Assertions.*; public class AwsProxyHttpServletRequestFormTest { @@ -34,6 +30,7 @@ public class AwsProxyHttpServletRequestFormTest { private static final String PART_KEY_2 = "test2"; private static final String PART_VALUE_2 = "value2"; private static final String FILE_KEY = "file_upload_1"; + private static final String FILE_KEY_2 = "file_upload_2"; private static final String FILE_NAME = "testImage.jpg"; private static final String ENCODED_VALUE = "test123a%3D1%262@3"; @@ -44,6 +41,7 @@ public class AwsProxyHttpServletRequestFormTest { .build(); private static final int FILE_SIZE = 512; private static byte[] FILE_BYTES = new byte[FILE_SIZE]; + private static byte[] FILE_BYTES_2 = new byte[FILE_SIZE]; static { new Random().nextBytes(FILE_BYTES); } @@ -52,15 +50,19 @@ public class AwsProxyHttpServletRequestFormTest { .addTextBody(PART_KEY_2, PART_VALUE_2) .addBinaryBody(FILE_KEY, FILE_BYTES, ContentType.IMAGE_JPEG, FILE_NAME) .build(); + private static final HttpEntity MULTIPART_BINARY_DATA_2 = MultipartEntityBuilder.create() + .addBinaryBody(FILE_KEY, FILE_BYTES, ContentType.IMAGE_JPEG, FILE_NAME) + .addBinaryBody(FILE_KEY, FILE_BYTES_2, ContentType.IMAGE_JPEG, FILE_NAME) + .build(); private static final String ENCODED_FORM_ENTITY = PART_KEY_1 + "=" + ENCODED_VALUE + "&" + PART_KEY_2 + "=" + PART_VALUE_2; @Test - public void postForm_getParam_getEncodedFullValue() { + void postForm_getParam_getEncodedFullValue() { try { AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/form", "POST") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED) - .body(ENCODED_FORM_ENTITY) - .build(); + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED) + .body(ENCODED_FORM_ENTITY) + .build(); HttpServletRequest request = new AwsProxyHttpServletRequest(proxyRequest, null, null); assertNotNull(request.getParts()); @@ -71,32 +73,32 @@ public void postForm_getParam_getEncodedFullValue() { } @Test - public void postForm_getParts_parsing() { + void postForm_getParts_parsing() { try { AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/form", "POST") - .header(MULTIPART_FORM_DATA.getContentType().getName(), MULTIPART_FORM_DATA.getContentType().getValue()) - //.header(formData.getContentEncoding().getName(), formData.getContentEncoding().getValue()) - .body(IOUtils.toString(MULTIPART_FORM_DATA.getContent())) - .build(); + .header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA.getContentType()) + //.header(formData.getContentEncoding().getName(), formData.getContentEncoding().getValue()) + .body(IOUtils.toString(MULTIPART_FORM_DATA.getContent(), Charset.defaultCharset())) + .build(); HttpServletRequest request = new AwsProxyHttpServletRequest(proxyRequest, null, null); assertNotNull(request.getParts()); assertEquals(2, request.getParts().size()); - assertEquals(PART_VALUE_1, IOUtils.toString(request.getPart(PART_KEY_1).getInputStream())); - assertEquals(PART_VALUE_2, IOUtils.toString(request.getPart(PART_KEY_2).getInputStream())); + assertEquals(PART_VALUE_1, IOUtils.toString(request.getPart(PART_KEY_1).getInputStream(), Charset.defaultCharset())); + assertEquals(PART_VALUE_2, IOUtils.toString(request.getPart(PART_KEY_2).getInputStream(), Charset.defaultCharset())); } catch (IOException | ServletException e) { fail(e.getMessage()); } } @Test - public void multipart_getParts_binary() { + void multipart_getParts_binary() { try { AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/form", "POST") - .header(MULTIPART_BINARY_DATA.getContentType().getName(), MULTIPART_BINARY_DATA.getContentType().getValue()) - .header(HttpHeaders.CONTENT_LENGTH, MULTIPART_BINARY_DATA.getContentLength() + "") - .binaryBody(MULTIPART_BINARY_DATA.getContent()) - .build(); + .header(HttpHeaders.CONTENT_TYPE, MULTIPART_BINARY_DATA.getContentType()) + .header(HttpHeaders.CONTENT_LENGTH, MULTIPART_BINARY_DATA.getContentLength() + "") + .binaryBody(MULTIPART_BINARY_DATA.getContent()) + .build(); HttpServletRequest request = new AwsProxyHttpServletRequest(proxyRequest, null, null); assertNotNull(request.getParts()); @@ -105,17 +107,41 @@ public void multipart_getParts_binary() { assertEquals(FILE_SIZE, request.getPart(FILE_KEY).getSize()); assertEquals(FILE_KEY, request.getPart(FILE_KEY).getName()); assertEquals(FILE_NAME, request.getPart(FILE_KEY).getSubmittedFileName()); - assertEquals(PART_VALUE_1, IOUtils.toString(request.getPart(PART_KEY_1).getInputStream())); - assertEquals(PART_VALUE_2, IOUtils.toString(request.getPart(PART_KEY_2).getInputStream())); + assertEquals(PART_VALUE_1, IOUtils.toString(request.getPart(PART_KEY_1).getInputStream(), Charset.defaultCharset())); + assertEquals(PART_VALUE_2, IOUtils.toString(request.getPart(PART_KEY_2).getInputStream(), Charset.defaultCharset())); } catch (IOException | ServletException e) { fail(e.getMessage()); } } @Test - public void postForm_getParamsBase64Encoded_expectAllParams() { + void multipart_getParts_returnsMultiplePartsWithSameFieldName() { + try { + AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/form", "POST") + .header(HttpHeaders.CONTENT_TYPE, MULTIPART_BINARY_DATA_2.getContentType()) + .header(HttpHeaders.CONTENT_LENGTH, MULTIPART_BINARY_DATA_2.getContentLength() + "") + .binaryBody(MULTIPART_BINARY_DATA_2.getContent()) + .build(); + + HttpServletRequest request = new AwsProxyHttpServletRequest(proxyRequest, null, null); + assertNotNull(request.getParts()); + assertEquals(2, request.getParts().size()); + assertNotNull(request.getPart(FILE_KEY)); + List partList = new ArrayList<>(request.getParts()); + assertEquals(partList.get(0).getSubmittedFileName(), partList.get(1).getSubmittedFileName()); + assertEquals(partList.get(0).getName(), partList.get(1).getName()); + assertEquals(FILE_SIZE, request.getPart(FILE_KEY).getSize()); + assertEquals(FILE_KEY, request.getPart(FILE_KEY).getName()); + assertEquals(FILE_NAME, request.getPart(FILE_KEY).getSubmittedFileName()); + } catch (IOException | ServletException e) { + fail(e.getMessage()); + } + } + + @Test + void postForm_getParamsBase64Encoded_expectAllParams() { AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/form", "POST") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED).build(); + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED).build(); proxyRequest.setBody(Base64.getEncoder().encodeToString(ENCODED_FORM_ENTITY.getBytes(Charset.defaultCharset()))); proxyRequest.setIsBase64Encoded(true); @@ -131,7 +157,7 @@ public void postForm_getParamsBase64Encoded_expectAllParams() { * issue #340 */ @Test - public void postForm_emptyParamPresent() { + void postForm_emptyParamPresent() { AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/form", "POST") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED).build(); String body = PART_KEY_1 + "=" + "&" + PART_KEY_2 + "=" + PART_VALUE_2; 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 166bfb472..f92c37168 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 @@ -6,15 +6,13 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; -import com.amazonaws.services.lambda.runtime.Context; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.SecurityContext; -import java.lang.reflect.Method; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.SecurityContext; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class AwsProxyHttpServletRequestReaderTest { @@ -26,7 +24,7 @@ public class AwsProxyHttpServletRequestReaderTest { private static final String DECODED_REQUEST_PATH = "/foo/bar/Some Thing"; @Test - public void readRequest_validAwsProxy_populatedRequest() { + void readRequest_validAwsProxy_populatedRequest() { AwsProxyRequest request = new AwsProxyRequestBuilder("/path", "GET").header(TEST_HEADER_KEY, TEST_HEADER_VALUE).build(); try { HttpServletRequest servletRequest = reader.readRequest(request, null, null, ContainerConfig.defaultConfig()); @@ -39,7 +37,7 @@ public void readRequest_validAwsProxy_populatedRequest() { } @Test - public void readRequest_urlDecode_expectDecodedPath() { + void readRequest_urlDecode_expectDecodedPath() { AwsProxyRequest request = new AwsProxyRequestBuilder(ENCODED_REQUEST_PATH, "GET").build(); try { HttpServletRequest servletRequest = reader.readRequest(request, null, null, ContainerConfig.defaultConfig()); @@ -54,7 +52,7 @@ public void readRequest_urlDecode_expectDecodedPath() { } @Test - public void readRequest_contentCharset_doesNotOverrideRequestCharset() { + void readRequest_contentCharset_doesNotOverrideRequestCharset() { String requestCharset = "application/json; charset=UTF-8"; AwsProxyRequest request = new AwsProxyRequestBuilder(ENCODED_REQUEST_PATH, "GET").header(HttpHeaders.CONTENT_TYPE, requestCharset).build(); try { @@ -70,7 +68,7 @@ public void readRequest_contentCharset_doesNotOverrideRequestCharset() { } @Test - public void readRequest_contentCharset_setsDefaultCharsetWhenNotSpecified() { + void readRequest_contentCharset_setsDefaultCharsetWhenNotSpecified() { String requestCharset = "application/json"; AwsProxyRequest request = new AwsProxyRequestBuilder(ENCODED_REQUEST_PATH, "GET").header(HttpHeaders.CONTENT_TYPE, requestCharset).build(); try { @@ -87,7 +85,7 @@ public void readRequest_contentCharset_setsDefaultCharsetWhenNotSpecified() { } @Test - public void readRequest_contentCharset_appendsCharsetToComplextContentType() { + void readRequest_contentCharset_appendsCharsetToComplextContentType() { String contentType = "multipart/form-data; boundary=something"; AwsProxyRequest request = new AwsProxyRequestBuilder(ENCODED_REQUEST_PATH, "GET").header(HttpHeaders.CONTENT_TYPE, contentType).build(); try { @@ -104,7 +102,7 @@ public void readRequest_contentCharset_appendsCharsetToComplextContentType() { } @Test - public void readRequest_validEventEmptyPath_expectException() { + void readRequest_validEventEmptyPath_expectException() { try { AwsProxyRequest req = new AwsProxyRequestBuilder(null, "GET").build(); HttpServletRequest servletReq = reader.readRequest(req, null, null, ContainerConfig.defaultConfig()); @@ -116,7 +114,7 @@ public void readRequest_validEventEmptyPath_expectException() { } @Test - public void readRequest_invalidEventEmptyMethod_expectException() { + void readRequest_invalidEventEmptyMethod_expectException() { try { AwsProxyRequest req = new AwsProxyRequestBuilder("/path", null).build(); reader.readRequest(req, null, null, ContainerConfig.defaultConfig()); @@ -127,7 +125,7 @@ public void readRequest_invalidEventEmptyMethod_expectException() { } @Test - public void readRequest_invalidEventEmptyContext_expectException() { + void readRequest_invalidEventEmptyContext_expectException() { try { AwsProxyRequest req = new AwsProxyRequestBuilder("/path", "GET").build(); req.setRequestContext(null); @@ -139,7 +137,7 @@ public void readRequest_invalidEventEmptyContext_expectException() { } @Test - public void readRequest_nullHeaders_expectSuccess() { + void readRequest_nullHeaders_expectSuccess() { AwsProxyRequest req = new AwsProxyRequestBuilder("/path", "GET").build(); req.setMultiValueHeaders(null); try { @@ -153,7 +151,7 @@ public void readRequest_nullHeaders_expectSuccess() { } @Test - public void readRequest_emptyHeaders_expectSuccess() { + void readRequest_emptyHeaders_expectSuccess() { AwsProxyRequest req = new AwsProxyRequestBuilder("/path", "GET").build(); try { HttpServletRequest servletReq = reader.readRequest(req, null, null, ContainerConfig.defaultConfig()); 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 de23d855c..736da27b4 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 @@ -5,14 +5,14 @@ import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.services.lambda.runtime.Context; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.SecurityContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.SecurityContext; import java.io.IOException; import java.io.InputStream; @@ -22,10 +22,9 @@ import java.time.ZonedDateTime; import java.util.*; -import static org.junit.Assert.*; -import static org.junit.Assume.assumeFalse; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.assumeFalse; -@RunWith(Parameterized.class) public class AwsProxyHttpServletRequestTest { private String requestType; @@ -34,7 +33,8 @@ public class AwsProxyHttpServletRequestTest { private static final String FORM_PARAM_NAME = "name"; private static final String FORM_PARAM_NAME_VALUE = "Stef"; private static final String FORM_PARAM_TEST = "test_cookie_param"; - private static final String QUERY_STRING_NAME_VALUE = "Bob"; + private static final String QUERY_STRING_NAME_VALUE = "Bob B!"; + private static final String QUERY_STRING_NAME = "name$"; private static final String REQUEST_SCHEME_HTTP = "http"; private static final String USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36"; private static final String REFERER = "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox"; @@ -66,6 +66,7 @@ public class AwsProxyHttpServletRequestTest { .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); @@ -73,18 +74,16 @@ public class AwsProxyHttpServletRequestTest { } private static final AwsProxyRequestBuilder REQUEST_QUERY = new AwsProxyRequestBuilder("/hello", "POST") - .queryString(FORM_PARAM_NAME, QUERY_STRING_NAME_VALUE); + .queryString(QUERY_STRING_NAME, QUERY_STRING_NAME_VALUE); private static final AwsProxyRequestBuilder REQUEST_QUERY_EMPTY_VALUE = new AwsProxyRequestBuilder("/hello", "POST") - .queryString(FORM_PARAM_NAME, ""); - + .queryString(QUERY_STRING_NAME, ""); - public AwsProxyHttpServletRequestTest(String type) { + public void initAwsProxyHttpServletRequestTest(String type) { requestType = type; } - @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API", "WRAP" }); + return Arrays.asList(new Object[]{"API_GW", "ALB", "HTTP_API", "WRAP"}); } private HttpServletRequest getRequest(AwsProxyRequestBuilder req, Context lambdaCtx, SecurityContext securityCtx) { @@ -103,17 +102,21 @@ private HttpServletRequest getRequest(AwsProxyRequestBuilder req, Context lambda } } - - @Test - public void headers_getHeader_validRequest() { + + @MethodSource("data") + @ParameterizedTest + void headers_getHeader_validRequest(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(getRequestWithHeaders(), null, null); assertNotNull(request.getHeader(CUSTOM_HEADER_KEY)); assertEquals(CUSTOM_HEADER_VALUE, request.getHeader(CUSTOM_HEADER_KEY)); assertEquals(MediaType.APPLICATION_JSON, request.getContentType()); } - @Test - public void headers_getRefererAndUserAgent_returnsContextValues() { + @MethodSource("data") + @ParameterizedTest + void headers_getRefererAndUserAgent_returnsContextValues(String type) { + initAwsProxyHttpServletRequestTest(type); assumeFalse("ALB".equals(requestType)); HttpServletRequest request = getRequest(REQUEST_USER_AGENT_REFERER, null, null); assertNotNull(request.getHeader("Referer")); @@ -125,45 +128,57 @@ public void headers_getRefererAndUserAgent_returnsContextValues() { assertEquals(USER_AGENT, request.getHeader("user-agent")); } - @Test - public void formParams_getParameter_validForm() { + @MethodSource("data") + @ParameterizedTest + void formParams_getParameter_validForm(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_FORM_URLENCODED, null, null); assertNotNull(request); assertNotNull(request.getParameter(FORM_PARAM_NAME)); assertEquals(FORM_PARAM_NAME_VALUE, request.getParameter(FORM_PARAM_NAME)); } - @Test - public void formParams_getParameter_null() { + @MethodSource("data") + @ParameterizedTest + void formParams_getParameter_null(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_INVALID_FORM_URLENCODED, null, null); assertNotNull(request); assertNull(request.getParameter(FORM_PARAM_NAME)); } - @Test - public void formParams_getParameter_multipleParams() { + @MethodSource("data") + @ParameterizedTest + void formParams_getParameter_multipleParams(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_FORM_URLENCODED_AND_QUERY, null, null); assertNotNull(request); assertEquals(2, request.getParameterValues(FORM_PARAM_NAME).length); } - @Test - public void formParams_getParameter_queryStringPrecendence() { + @MethodSource("data") + @ParameterizedTest + void formParams_getParameter_queryStringPrecendence(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_FORM_URLENCODED_AND_QUERY, null, null); assertNotNull(request); assertEquals(2, request.getParameterValues(FORM_PARAM_NAME).length); assertEquals(QUERY_STRING_NAME_VALUE, request.getParameter(FORM_PARAM_NAME)); } - @Test - public void dateHeader_noDate_returnNegativeOne() { + @MethodSource("data") + @ParameterizedTest + void dateHeader_noDate_returnNegativeOne(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_FORM_URLENCODED_AND_QUERY, null, null); assertNotNull(request); assertEquals(-1L, request.getDateHeader(HttpHeaders.DATE)); } - @Test - public void dateHeader_correctDate_parseToCorrectLong() { + @MethodSource("data") + @ParameterizedTest + void dateHeader_correctDate_parseToCorrectLong(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_WITH_DATE, null, null); assertNotNull(request); @@ -172,32 +187,40 @@ public void dateHeader_correctDate_parseToCorrectLong() { assertEquals(-1L, request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE)); } - @Test - public void scheme_getScheme_https() { + @MethodSource("data") + @ParameterizedTest + void scheme_getScheme_https(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_FORM_URLENCODED, null, null); assertNotNull(request); assertNotNull(request.getScheme()); assertEquals("https", request.getScheme()); } - @Test - public void scheme_getScheme_http() { + @MethodSource("data") + @ParameterizedTest + void scheme_getScheme_http(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(getRequestWithHeaders(), null, null); assertNotNull(request); assertNotNull(request.getScheme()); assertEquals(REQUEST_SCHEME_HTTP, request.getScheme()); } - @Test - public void cookie_getCookies_noCookies() { + @MethodSource("data") + @ParameterizedTest + void cookie_getCookies_noCookies(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(getRequestWithHeaders(), null, null); assertNotNull(request); assertNotNull(request.getCookies()); assertEquals(0, request.getCookies().length); } - @Test - public void cookie_getCookies_singleCookie() { + @MethodSource("data") + @ParameterizedTest + void cookie_getCookies_singleCookie(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_SINGLE_COOKIE, null, null); assertNotNull(request); assertNotNull(request.getCookies()); @@ -206,8 +229,10 @@ public void cookie_getCookies_singleCookie() { assertEquals(FORM_PARAM_NAME_VALUE, request.getCookies()[0].getValue()); } - @Test - public void cookie_getCookies_multipleCookies() { + @MethodSource("data") + @ParameterizedTest + void cookie_getCookies_multipleCookies(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_MULTIPLE_COOKIES, null, null); assertNotNull(request); assertNotNull(request.getCookies()); @@ -218,56 +243,70 @@ public void cookie_getCookies_multipleCookies() { assertEquals(FORM_PARAM_NAME_VALUE, request.getCookies()[1].getValue()); } - @Test - public void cookie_getCookies_emptyCookies() { + @MethodSource("data") + @ParameterizedTest + void cookie_getCookies_emptyCookies(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_MALFORMED_COOKIE, null, null); assertNotNull(request); assertNotNull(request.getCookies()); assertEquals(0, request.getCookies().length); } - @Test - public void queryParameters_getParameterMap_null() { + @MethodSource("data") + @ParameterizedTest + void queryParameters_getParameterMap_null(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_NULL_QUERY_STRING, null, null); assertNotNull(request); assertEquals(0, request.getParameterMap().size()); } - @Test - public void queryParameters_getParameterMap_nonNull() { + @MethodSource("data") + @ParameterizedTest + void queryParameters_getParameterMap_nonNull(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_QUERY, null, null); assertNotNull(request); assertEquals(1, request.getParameterMap().size()); - assertEquals(QUERY_STRING_NAME_VALUE, request.getParameterMap().get(FORM_PARAM_NAME)[0]); + assertEquals(QUERY_STRING_NAME_VALUE, request.getParameterMap().get(QUERY_STRING_NAME)[0]); } - @Test - public void queryParameters_getParameterMap_nonNull_EmptyParamValue() { + @MethodSource("data") + @ParameterizedTest + void queryParameters_getParameterMap_nonNull_EmptyParamValue(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_QUERY_EMPTY_VALUE, null, null); assertNotNull(request); assertEquals(1, request.getParameterMap().size()); - assertEquals("", request.getParameterMap().get(FORM_PARAM_NAME)[0]); + assertEquals("", request.getParameterMap().get(QUERY_STRING_NAME)[0]); } - @Test - public void queryParameters_getParameterNames_null() { + @MethodSource("data") + @ParameterizedTest + void queryParameters_getParameterNames_null(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_NULL_QUERY_STRING, null, null); List parameterNames = Collections.list(request.getParameterNames()); assertNotNull(request); assertEquals(0, parameterNames.size()); } - @Test - public void queryParameters_getParameterNames_notNull() { + @MethodSource("data") + @ParameterizedTest + void queryParameters_getParameterNames_notNull(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_QUERY, null, null); List parameterNames = Collections.list(request.getParameterNames()); assertNotNull(request); assertEquals(1, parameterNames.size()); - assertTrue(parameterNames.contains(FORM_PARAM_NAME)); + assertTrue(parameterNames.contains(QUERY_STRING_NAME)); } - @Test - public void queryParameter_getParameterMap_avoidDuplicationOnMultipleCalls() { + @MethodSource("data") + @ParameterizedTest + void queryParameter_getParameterMap_avoidDuplicationOnMultipleCalls(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_MULTIPLE_FORM_AND_QUERY, null, null); Map params = request.getParameterMap(); @@ -287,23 +326,27 @@ public void queryParameter_getParameterMap_avoidDuplicationOnMultipleCalls() { assertEquals(1, params.get(FORM_PARAM_TEST).length); } - @Test - public void charEncoding_getEncoding_expectNoEncodingWithoutContentType() { - HttpServletRequest request = getRequest(REQUEST_SINGLE_COOKIE, null, null); - try { - request.setCharacterEncoding(StandardCharsets.UTF_8.name()); - // we have not specified a content type so the encoding will not be set - assertNull(request.getCharacterEncoding()); - assertNull(request.getContentType()); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - fail("Unsupported encoding"); + @MethodSource("data") + @ParameterizedTest + void charEncoding_getEncoding_expectNoEncodingWithoutContentType(String type) { + initAwsProxyHttpServletRequestTest(type); + HttpServletRequest request = getRequest(REQUEST_SINGLE_COOKIE, null, null); + try { + request.setCharacterEncoding(StandardCharsets.UTF_8.name()); + // we have not specified a content type so the encoding will not be set + assertNull(request.getCharacterEncoding()); + assertNull(request.getContentType()); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + fail("Unsupported encoding"); - } + } } - @Test - public void charEncoding_getEncoding_expectContentTypeOnly() { + @MethodSource("data") + @ParameterizedTest + void charEncoding_getEncoding_expectContentTypeOnly(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(getRequestWithHeaders(), null, null); // we have not specified a content type so the encoding will not be set assertNull(request.getCharacterEncoding()); @@ -320,11 +363,13 @@ public void charEncoding_getEncoding_expectContentTypeOnly() { } } - @Test - public void charEncoding_addCharEncodingTwice_expectSingleMediaTypeAndEncoding() { + @MethodSource("data") + @ParameterizedTest + void charEncoding_addCharEncodingTwice_expectSingleMediaTypeAndEncoding(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(getRequestWithHeaders(), null, null); // we have not specified a content type so the encoding will not be set - assertEquals(null, request.getCharacterEncoding()); + assertNull(request.getCharacterEncoding()); assertEquals(MediaType.APPLICATION_JSON, request.getContentType()); try { @@ -346,8 +391,10 @@ public void charEncoding_addCharEncodingTwice_expectSingleMediaTypeAndEncoding() } } - @Test - public void contentType_lowerCaseHeaderKey_expectUpdatedMediaType() { + @MethodSource("data") + @ParameterizedTest + void contentType_lowerCaseHeaderKey_expectUpdatedMediaType(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest request = getRequest(REQUEST_WITH_LOWERCASE_HEADER, null, null); try { request.setCharacterEncoding(StandardCharsets.UTF_8.name()); @@ -361,8 +408,10 @@ public void contentType_lowerCaseHeaderKey_expectUpdatedMediaType() { } } - @Test - public void contentType_duplicateCase_expectSingleContentTypeHeader() { + @MethodSource("data") + @ParameterizedTest + void contentType_duplicateCase_expectSingleContentTypeHeader(String type) { + initAwsProxyHttpServletRequestTest(type); AwsProxyRequestBuilder proxyRequest = getRequestWithHeaders(); HttpServletRequest request = getRequest(proxyRequest, null, null); @@ -376,8 +425,10 @@ public void contentType_duplicateCase_expectSingleContentTypeHeader() { } } - @Test - public void requestURL_getUrl_expectHttpSchemaAndLocalhostForLocalTesting() { + @MethodSource("data") + @ParameterizedTest + void requestURL_getUrl_expectHttpSchemaAndLocalhostForLocalTesting(String type) { + initAwsProxyHttpServletRequestTest(type); assumeFalse("ALB".equals(requestType)); AwsProxyRequestBuilder req = getRequestWithHeaders(); req.apiId("test-id"); @@ -397,8 +448,10 @@ public void requestURL_getUrl_expectHttpSchemaAndLocalhostForLocalTesting() { LambdaContainerHandler.getContainerConfig().getCustomDomainNames().remove("localhost"); } - @Test - public void requestURL_getUrlWithCustomBasePath_expectCustomBasePath() { + @MethodSource("data") + @ParameterizedTest + void requestURL_getUrlWithCustomBasePath_expectCustomBasePath(String type) { + initAwsProxyHttpServletRequestTest(type); AwsProxyRequestBuilder req = getRequestWithHeaders(); LambdaContainerHandler.getContainerConfig().setServiceBasePath("test"); HttpServletRequest servletRequest = getRequest(req, null, null); @@ -407,21 +460,24 @@ public void requestURL_getUrlWithCustomBasePath_expectCustomBasePath() { LambdaContainerHandler.getContainerConfig().setServiceBasePath(null); } - @Test - public void requestURL_getUrlWithContextPath_expectStageAsContextPath() { + @MethodSource("data") + @ParameterizedTest + void requestURL_getUrlWithContextPath_expectStageAsContextPath(String type) { + initAwsProxyHttpServletRequestTest(type); 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() { + @MethodSource("data") + @ParameterizedTest + void getLocales_emptyAcceptHeader_expectDefaultLocale(String type) { + initAwsProxyHttpServletRequestTest(type); AwsProxyRequestBuilder req = getRequestWithHeaders(); HttpServletRequest servletRequest = getRequest(req, null, null); Enumeration locales = servletRequest.getLocales(); @@ -434,8 +490,10 @@ public void getLocales_emptyAcceptHeader_expectDefaultLocale() { assertEquals(1, localesNo); } - @Test - public void getLocales_validAcceptHeader_expectSingleLocale() { + @MethodSource("data") + @ParameterizedTest + void getLocales_validAcceptHeader_expectSingleLocale(String type) { + initAwsProxyHttpServletRequestTest(type); AwsProxyRequestBuilder req = getRequestWithHeaders(); req.header(HttpHeaders.ACCEPT_LANGUAGE, "fr-CH"); HttpServletRequest servletRequest = getRequest(req, null, null); @@ -443,16 +501,18 @@ public void getLocales_validAcceptHeader_expectSingleLocale() { int localesNo = 0; while (locales.hasMoreElements()) { Locale defaultLocale = locales.nextElement(); - assertEquals(new Locale("fr-CH"), defaultLocale); + assertEquals(new Locale("fr", "CH"), defaultLocale); localesNo++; } assertEquals(1, localesNo); } - @Test - public void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleList() { + @MethodSource("data") + @ParameterizedTest + void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleList(String type) { + initAwsProxyHttpServletRequestTest(type); 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"); + req.header(HttpHeaders.ACCEPT_LANGUAGE, "fr-CA, 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<>(); @@ -460,20 +520,22 @@ public void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleList() { localesList.add(locales.nextElement()); } assertEquals(5, localesList.size()); - assertEquals(new Locale("fr-CH"), localesList.get(0)); - assertEquals(new Locale("fr"), localesList.get(1)); - assertEquals(new Locale("en"), localesList.get(2)); + assertEquals(Locale.CANADA_FRENCH, localesList.get(0)); + assertEquals(Locale.FRENCH, localesList.get(1)); + assertEquals(Locale.ENGLISH, localesList.get(2)); assertEquals(new Locale("de"), localesList.get(3)); assertEquals(new Locale("*"), localesList.get(4)); assertNotNull(servletRequest.getLocale()); - assertEquals(new Locale("fr-CH"), servletRequest.getLocale()); + assertEquals(Locale.CANADA_FRENCH, servletRequest.getLocale()); } - @Test - public void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleListOrdered() { + @MethodSource("data") + @ParameterizedTest + void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleListOrdered(String type) { + initAwsProxyHttpServletRequestTest(type); 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"); + req.header(HttpHeaders.ACCEPT_LANGUAGE, "fr-CA, 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<>(); @@ -481,15 +543,17 @@ public void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleListOrde localesList.add(locales.nextElement()); } assertEquals(5, localesList.size()); - assertEquals(new Locale("fr-CH"), localesList.get(0)); - assertEquals(new Locale("fr"), localesList.get(1)); - assertEquals(new Locale("en"), localesList.get(2)); + assertEquals(Locale.CANADA_FRENCH, localesList.get(0)); + assertEquals(Locale.FRENCH, localesList.get(1)); + assertEquals(Locale.ENGLISH, localesList.get(2)); assertEquals(new Locale("de"), localesList.get(3)); assertEquals(new Locale("*"), localesList.get(4)); } - @Test - public void nullQueryString_expectNoExceptions() { + @MethodSource("data") + @ParameterizedTest + void nullQueryString_expectNoExceptions(String type) { + initAwsProxyHttpServletRequestTest(type); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/hello", "GET"); HttpServletRequest servletReq = getRequest(req, null, null); assertNull(servletReq.getQueryString()); @@ -499,8 +563,10 @@ public void nullQueryString_expectNoExceptions() { assertNull(servletReq.getParameterValues("param")); } - @Test - public void inputStream_emptyBody_expectNullInputStream() { + @MethodSource("data") + @ParameterizedTest + void inputStream_emptyBody_expectNullInputStream(String type) { + initAwsProxyHttpServletRequestTest(type); AwsProxyRequestBuilder proxyReq = getRequestWithHeaders(); assertNull(proxyReq.build().getBody()); HttpServletRequest req = getRequest(proxyReq, null, null); @@ -514,37 +580,47 @@ public void inputStream_emptyBody_expectNullInputStream() { } } - @Test - public void getHeaders_emptyHeaders_expectEmptyEnumeration() { + @MethodSource("data") + @ParameterizedTest + void getHeaders_emptyHeaders_expectEmptyEnumeration(String type) { + initAwsProxyHttpServletRequestTest(type); AwsProxyRequestBuilder proxyReq = new AwsProxyRequestBuilder("/hello", "GET"); HttpServletRequest req = getRequest(proxyReq, null, null); assertFalse(req.getHeaders("param").hasMoreElements()); } - @Test - public void getServerPort_defaultPort_expect443() { + @MethodSource("data") + @ParameterizedTest + void getServerPort_defaultPort_expect443(String type) { + initAwsProxyHttpServletRequestTest(type); HttpServletRequest req = getRequest(getRequestWithHeaders(), null, null); assertEquals(443, req.getServerPort()); } - @Test - public void getServerPort_customPortFromHeader_expectCustomPort() { + @MethodSource("data") + @ParameterizedTest + void getServerPort_customPortFromHeader_expectCustomPort(String type) { + initAwsProxyHttpServletRequestTest(type); 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() { + @MethodSource("data") + @ParameterizedTest + void getServerPort_invalidCustomPortFromHeader_expectDefaultPort(String type) { + initAwsProxyHttpServletRequestTest(type); 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() { + @MethodSource("data") + @ParameterizedTest + void serverName_emptyHeaders_doesNotThrowNullPointer(String type) { + initAwsProxyHttpServletRequestTest(type); AwsProxyRequestBuilder proxyReq = new AwsProxyRequestBuilder("/test", "GET"); proxyReq.multiValueHeaders(null); HttpServletRequest servletReq = getRequest(proxyReq, null, null); @@ -552,8 +628,10 @@ public void serverName_emptyHeaders_doesNotThrowNullPointer() { assertTrue(serverName.startsWith("null.execute-api")); } - @Test - public void serverName_hostHeader_returnsHostHeaderOnly() { + @MethodSource("data") + @ParameterizedTest + void serverName_hostHeader_returnsHostHeaderOnly(String type) { + initAwsProxyHttpServletRequestTest(type); AwsProxyRequestBuilder proxyReq = new AwsProxyRequestBuilder("/test", "GET") .header(HttpHeaders.HOST, "testapi.com"); LambdaContainerHandler.getContainerConfig().addCustomDomain("testapi.com"); @@ -562,10 +640,46 @@ public void serverName_hostHeader_returnsHostHeaderOnly() { assertEquals("testapi.com", serverName); } + @Test + void serverName_albHostHeader_returnsHostHeader() { + initAwsProxyHttpServletRequestTest("ALB"); + AwsProxyRequestBuilder proxyReq = new AwsProxyRequestBuilder("/test", "GET") + .header(HttpHeaders.HOST, "testapi.us-east-1.elb.amazonaws.com"); + HttpServletRequest servletReq = getRequest(proxyReq, null, null); + String serverName = servletReq.getServerName(); + assertEquals("testapi.us-east-1.elb.amazonaws.com", serverName); + } + + @Test + void getRemoteHost_albHostHeader_singleValue_returnsHostHeader() { + initAwsProxyHttpServletRequestTest("ALB"); + AwsProxyRequest proxyReq = new AwsProxyRequestBuilder("/test", "GET") + .alb().build(); + proxyReq.setMultiValueHeaders(null); + proxyReq.getHeaders().put(HttpHeaders.HOST, "testapi.us-east-1.elb.amazonaws.com"); + HttpServletRequest servletRequest = new AwsProxyHttpServletRequest(proxyReq, null, null); + + String host = servletRequest.getRemoteHost(); + assertEquals("testapi.us-east-1.elb.amazonaws.com", host); + } + + @Test + void getRemoteHost_albHostHeader_multiValue_returnsHostHeader() { + initAwsProxyHttpServletRequestTest("ALB"); + AwsProxyRequest proxyReq = new AwsProxyRequestBuilder("/test", "GET") + .header(HttpHeaders.HOST, "testapi.us-east-1.elb.amazonaws.com") + .alb().build(); + proxyReq.setHeaders(null); + HttpServletRequest servletRequest = new AwsProxyHttpServletRequest(proxyReq, null, null); + + String host = servletRequest.getRemoteHost(); + assertEquals("testapi.us-east-1.elb.amazonaws.com", host); + } + 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); + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .header(AwsProxyHttpServletRequest.CF_PROTOCOL_HEADER_NAME, REQUEST_SCHEME_HTTP); } } 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 d061a1115..254ce04a0 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 @@ -10,20 +10,18 @@ import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.services.lambda.runtime.Context; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper; -import javax.servlet.Servlet; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.Servlet; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.CountDownLatch; -import static junit.framework.TestCase.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class AwsProxyRequestDispatcherTest { public static final String FORWARD_PATH = "/newpath"; @@ -31,9 +29,9 @@ public class AwsProxyRequestDispatcherTest { @Test - public void setPath_forwardByPath_proxyRequestObjectInPropertyReferencesSameProxyRequest() throws InvalidRequestEventException { + void setPath_forwardByPath_proxyRequestObjectInPropertyReferencesSameProxyRequest() throws InvalidRequestEventException { AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); - HttpServletRequest 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); @@ -41,9 +39,9 @@ public void setPath_forwardByPath_proxyRequestObjectInPropertyReferencesSameProx } @Test - public void setPathForWrappedRequest_forwardByPath_proxyRequestObjectInPropertyReferencesSameProxyRequest() throws InvalidRequestEventException { + void setPathForWrappedRequest_forwardByPath_proxyRequestObjectInPropertyReferencesSameProxyRequest() throws InvalidRequestEventException { AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); - HttpServletRequest 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); @@ -52,7 +50,7 @@ public void setPathForWrappedRequest_forwardByPath_proxyRequestObjectInPropertyR } @Test - public void setPathForWrappedRequestWithoutGatewayEvent_forwardByPath_throwsException() { + void setPathForWrappedRequestWithoutGatewayEvent_forwardByPath_throwsException() { AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); AwsProxyHttpServletRequest servletRequest = new AwsProxyHttpServletRequest(proxyRequest, new MockLambdaContext(), null); SecurityContextHolderAwareRequestWrapper springSecurityRequest = new SecurityContextHolderAwareRequestWrapper(servletRequest, "ADMIN"); @@ -68,9 +66,9 @@ public void setPathForWrappedRequestWithoutGatewayEvent_forwardByPath_throwsExce } @Test - public void forwardRequest_nullHandler_throwsIllegalStateException() throws InvalidRequestEventException { + void forwardRequest_nullHandler_throwsIllegalStateException() throws InvalidRequestEventException { AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); - HttpServletRequest 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))); @@ -86,9 +84,9 @@ public void forwardRequest_nullHandler_throwsIllegalStateException() throws Inva } @Test - public void forwardRequest_committedResponse_throwsIllegalStateException() throws InvalidRequestEventException { + void forwardRequest_committedResponse_throwsIllegalStateException() throws InvalidRequestEventException { AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); - HttpServletRequest 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)); @@ -107,9 +105,9 @@ public void forwardRequest_committedResponse_throwsIllegalStateException() throw } @Test - public void forwardRequest_partiallyWrittenResponse_resetsBuffer() throws InvalidRequestEventException { + void forwardRequest_partiallyWrittenResponse_resetsBuffer() throws InvalidRequestEventException { AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); - HttpServletRequest 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)); @@ -127,12 +125,12 @@ public void forwardRequest_partiallyWrittenResponse_resetsBuffer() throws Invali } @Test - public void include_addsToResponse_appendsCorrectly() throws InvalidRequestEventException, IOException { + void include_addsToResponse_appendsCorrectly() throws InvalidRequestEventException, IOException { final String firstPart = "first"; final String secondPart = "second"; AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); - AwsProxyResponse resp = mockLambdaHandler((AwsProxyHttpServletRequest req, AwsHttpServletResponse res)-> { + AwsProxyResponse resp = mockLambdaHandler((AwsProxyHttpServletRequest req, AwsHttpServletResponse res) -> { if (req.getAttribute("cnt") == null) { res.getOutputStream().write(firstPart.getBytes()); req.setAttribute("cnt", 1); @@ -147,13 +145,13 @@ public void include_addsToResponse_appendsCorrectly() throws InvalidRequestEvent } @Test - public void include_appendsNewHeader_cannotAppendNewHeaders() throws InvalidRequestEventException, IOException { + void include_appendsNewHeader_cannotAppendNewHeaders() throws InvalidRequestEventException, IOException { final String firstPart = "first"; final String secondPart = "second"; final String headerKey = "X-Custom-Header"; AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); - AwsProxyResponse resp = mockLambdaHandler((AwsProxyHttpServletRequest req, AwsHttpServletResponse res)-> { + AwsProxyResponse resp = mockLambdaHandler((AwsProxyHttpServletRequest req, AwsHttpServletResponse res) -> { if (req.getAttribute("cnt") == null) { res.getOutputStream().write(firstPart.getBytes()); req.setAttribute("cnt", 1); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java index 07628f187..58d0b0542 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java @@ -4,30 +4,25 @@ import com.amazonaws.serverless.proxy.internal.servlet.filters.UrlPathValidator; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.concurrent.CountDownLatch; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class AwsServletContextTest { private static String TMP_DIR = System.getProperty("java.io.tmpdir"); private static final AwsServletContext STATIC_CTX = new AwsServletContext(null); - @BeforeClass + @BeforeAll public static void setUp() { LambdaContainerHandler.getContainerConfig().addValidFilePath("/private/var/task"); File tmpFile = new File(TMP_DIR); @@ -40,8 +35,9 @@ public static void setUp() { LambdaContainerHandler.getContainerConfig().addValidFilePath("C:\\MyTestFolder"); } - @Test @Ignore - public void getMimeType_disabledPath_expectException() { + @Test + @Disabled + void getMimeType_disabledPath_expectException() { AwsServletContext ctx = new AwsServletContext(null); try { assertNull(ctx.getMimeType("/usr/local/lib/nothing")); @@ -54,13 +50,13 @@ public void getMimeType_disabledPath_expectException() { } @Test - public void getMimeType_nonExistentFileInTaskPath_expectNull() { + void getMimeType_nonExistentFileInTaskPath_expectNull() { AwsServletContext ctx = new AwsServletContext(null); assertNull(ctx.getMimeType("/var/task/nothing")); } @Test - public void getMimeType_mimeTypeOfCorrectFile_expectMime() { + void getMimeType_mimeTypeOfCorrectFile_expectMime() { String tmpFilePath = TMP_DIR + "test_text.txt"; AwsServletContext ctx = new AwsServletContext(null); String mimeType = ctx.getMimeType(tmpFilePath); @@ -69,17 +65,24 @@ public void getMimeType_mimeTypeOfCorrectFile_expectMime() { mimeType = ctx.getMimeType("file://" + tmpFilePath); assertEquals("text/plain", mimeType); } + @Test + void getMimeType_mimeTypeOfJavascript_expectApplicationJavascript() { + String tmpFilePath = TMP_DIR + "some.js"; + AwsServletContext ctx = new AwsServletContext(null); + String mimeType = ctx.getMimeType(tmpFilePath); + assertEquals("text/javascript", mimeType); + } @Test - public void getMimeType_unknownExtension_expectAppOctetStream() { + void getMimeType_unknownExtension_expectNull() { AwsServletContext ctx = new AwsServletContext(null); String mimeType = ctx.getMimeType("myfile.unkext"); - assertEquals("application/octet-stream", mimeType); + assertNull(mimeType); } @Test - public void addFilter_nonExistentFilterClass_expectException() { + void addFilter_nonExistentFilterClass_expectException() { AwsServletContext ctx = new AwsServletContext(null); String filterClass = "com.amazonaws.serverless.TestingFilterClassNonExistent"; try { @@ -92,7 +95,7 @@ public void addFilter_nonExistentFilterClass_expectException() { } @Test - public void addFilter_doesNotImplementFilter_expectException() { + void addFilter_doesNotImplementFilter_expectException() { AwsServletContext ctx = new AwsServletContext(null); try { ctx.addFilter("filter", this.getClass().getName()); @@ -104,7 +107,7 @@ public void addFilter_doesNotImplementFilter_expectException() { } @Test - public void addFilter_validFilter_expectSuccess() { + void addFilter_validFilter_expectSuccess() { AwsServletContext ctx = new AwsServletContext(null); FilterRegistration.Dynamic reg = ctx.addFilter("filter", UrlPathValidator.class.getName()); assertNotNull(reg); @@ -118,7 +121,7 @@ public void addFilter_validFilter_expectSuccess() { } @Test - public void addFilter_validFilter_expectSuccessWithCustomFilterName() { + void addFilter_validFilter_expectSuccessWithCustomFilterName() { AwsServletContext ctx = new AwsServletContext(null); FilterRegistration.Dynamic reg = ctx.addFilter("filter", TestFilter.class.getName()); assertNotNull(reg); @@ -132,18 +135,18 @@ public void addFilter_validFilter_expectSuccessWithCustomFilterName() { } @Test - public void getContextPath_expectEmpty() { + void getContextPath_expectEmpty() { assertEquals("", STATIC_CTX.getContextPath()); } @Test - public void getContext_returnsSameContext() { + void getContext_returnsSameContext() { assertEquals(STATIC_CTX, STATIC_CTX.getContext("1")); assertEquals(STATIC_CTX, STATIC_CTX.getContext("2")); } @Test - public void getVersions_expectStaticVersions() { + void getVersions_expectStaticVersions() { assertEquals(AwsServletContext.SERVLET_API_MAJOR_VERSION, STATIC_CTX.getMajorVersion()); assertEquals(AwsServletContext.SERVLET_API_MINOR_VERSION, STATIC_CTX.getMinorVersion()); assertEquals(AwsServletContext.SERVLET_API_MAJOR_VERSION, STATIC_CTX.getEffectiveMajorVersion()); @@ -151,7 +154,7 @@ public void getVersions_expectStaticVersions() { } @Test - public void startAsync_expectPopulatedAsyncContext() { + void startAsync_expectPopulatedAsyncContext() { HttpServletRequest req = new AwsProxyHttpServletRequest( new AwsProxyRequestBuilder("/", "GET").build(), null, @@ -164,7 +167,7 @@ public void startAsync_expectPopulatedAsyncContext() { } @Test - public void startAsyncWithNewRequest_expectPopulatedAsyncContext() { + void startAsyncWithNewRequest_expectPopulatedAsyncContext() { HttpServletRequest req = new AwsProxyHttpServletRequest( new AwsProxyRequestBuilder("/", "GET").build(), null, @@ -182,25 +185,20 @@ public void startAsyncWithNewRequest_expectPopulatedAsyncContext() { } @Test - public void unsupportedOperations_expectExceptions() { + void unsupportedOperations_expectExceptions() { int exCount = 0; try { STATIC_CTX.getResourcePaths("1"); } catch (UnsupportedOperationException e) { exCount++; } - try { - STATIC_CTX.getNamedDispatcher("1"); - } catch (UnsupportedOperationException e) { - exCount++; - } - assertEquals(2, exCount); + assertEquals(1, exCount); assertNull(STATIC_CTX.getServletRegistration("1")); } @Test - public void servletMappings_expectCorrectServlet() { + void servletMappings_expectCorrectServlet() { AwsServletContext ctx = new AwsServletContext(null); TestServlet srv1 = new TestServlet("srv1"); TestServlet srv2 = new TestServlet("srv2"); @@ -223,12 +221,17 @@ public void servletMappings_expectCorrectServlet() { } @Test - public void addServlet_callsDefaultConstructor() throws ServletException { + void addServlet_callsDefaultConstructor() throws ServletException { AwsServletContext ctx = new AwsServletContext(null); ctx.addServlet("srv1", TestServlet.class); - assertNotNull(ctx.getServlet("srv1")); - assertNotNull(ctx.getServletRegistration("srv1")); - assertEquals("", ((TestServlet)ctx.getServlet("srv1")).getId()); + assertNotNull(((AwsServletRegistration) ctx.getServletRegistration("srv1")).getServlet()); + assertEquals("", ((TestServlet)((AwsServletRegistration) ctx.getServletRegistration("srv1")).getServlet()).getId()); + } + + @Test + void getNamedDispatcher_returnsDispatcher() { + AwsServletContext ctx = new AwsServletContext(null); + assertNotNull(ctx.getNamedDispatcher("/hello")); } public static class TestServlet implements Servlet { diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletRegistrationTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletRegistrationTest.java index b823da10f..80bb38060 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletRegistrationTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletRegistrationTest.java @@ -1,8 +1,8 @@ package com.amazonaws.serverless.proxy.internal.servlet; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import javax.servlet.*; +import jakarta.servlet.*; import java.io.IOException; import java.util.Enumeration; @@ -10,12 +10,12 @@ import java.util.Map; import java.util.Set; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class AwsServletRegistrationTest { @Test - public void getMappings_singleMapping_savedCorrectly() { + void getMappings_singleMapping_savedCorrectly() { ServletRegistration.Dynamic reg = new AwsServletRegistration("test", null, new AwsServletContext(null)); reg.addMapping("/"); assertEquals(1, reg.getMappings().size()); @@ -27,7 +27,7 @@ public void getMappings_singleMapping_savedCorrectly() { } @Test - public void metadata_savedAndReturnedCorrectly() { + void metadata_savedAndReturnedCorrectly() { ServletRegistration.Dynamic reg = new AwsServletRegistration("test", null, new AwsServletContext(null)); assertEquals("test", reg.getName()); reg.setLoadOnStartup(2); @@ -40,7 +40,7 @@ public void metadata_savedAndReturnedCorrectly() { } @Test - public void setInitParameter_savedCorrectly() { + void setInitParameter_savedCorrectly() { ServletRegistration.Dynamic reg = new AwsServletRegistration("test", null, new AwsServletContext(null)); assertTrue(reg.setInitParameter("param", "value")); assertFalse(reg.setInitParameter("param", "value")); @@ -55,7 +55,7 @@ public void setInitParameter_savedCorrectly() { } @Test - public void servletConfig_populatesConfig() throws ServletException { + void servletConfig_populatesConfig() throws ServletException { AwsServletContext servletCtx = new AwsServletContext(null); TestServlet servlet = new TestServlet(); ServletRegistration.Dynamic reg = new AwsServletRegistration("test", servlet, servletCtx); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolderTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolderTest.java index 6c39c8d8d..1180a5d15 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolderTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolderTest.java @@ -3,15 +3,15 @@ import com.amazonaws.serverless.proxy.internal.servlet.filters.UrlPathValidator; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.services.lambda.runtime.Context; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class FilterHolderTest { private static Context lambdaContext = new MockLambdaContext(); @Test - public void annotation_filterRegistration_pathValidator() { + void annotation_filterRegistration_pathValidator() { FilterHolder holder = new FilterHolder(new UrlPathValidator(), new AwsServletContext(null)); assertTrue(holder.isAnnotated()); 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 1481afc7e..73f8d7908 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 @@ -6,17 +6,17 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.services.lambda.runtime.Context; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.concurrent.CountDownLatch; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class ServletLambdaContainerHandlerBuilderTest { @Test - public void validation_throwsException() { + void validation_throwsException() { TestBuilder testBuilder = new TestBuilder(); try { testBuilder.validate(); @@ -27,21 +27,21 @@ public void validation_throwsException() { } @Test - public void additionalMethod_testSetter() { + void additionalMethod_testSetter() { TestBuilder test = new TestBuilder().exceptionHandler(new AwsProxyExceptionHandler()).name("test"); assertEquals("test", test.getName()); } @Test - public void defaultProxy_setsValuesCorrectly() { + void defaultProxy_setsValuesCorrectly() { TestBuilder test = new TestBuilder().defaultProxy().name("test"); assertNotNull(test.initializationWrapper); assertTrue(test.exceptionHandler instanceof AwsProxyExceptionHandler); assertTrue(test.requestReader instanceof AwsProxyHttpServletRequestReader); assertTrue(test.responseWriter instanceof AwsProxyHttpServletResponseWriter); assertTrue(test.securityContextWriter instanceof AwsProxySecurityContextWriter); - assertSame(test.requestTypeClass, AwsProxyRequest.class); - assertSame(test.responseTypeClass, AwsProxyResponse.class); + assertSame(AwsProxyRequest.class, test.requestTypeClass); + assertSame(AwsProxyResponse.class, test.responseTypeClass); assertEquals("test", test.name); } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidatorTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidatorTest.java index beb273d56..fa6712c7c 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidatorTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidatorTest.java @@ -1,28 +1,25 @@ package com.amazonaws.serverless.proxy.internal.servlet.filters; -import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest; import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; -import com.amazonaws.serverless.proxy.internal.servlet.FilterHolder; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import javax.servlet.FilterConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; -import java.io.IOException; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; public class UrlPathValidatorTest { @Test - public void init_noConfig_setsDefaultStatusCode() { + void init_noConfig_setsDefaultStatusCode() { UrlPathValidator pathValidator = new UrlPathValidator(); try { pathValidator.init(null); @@ -34,7 +31,7 @@ public void init_noConfig_setsDefaultStatusCode() { } @Test - public void init_withConfig_setsCorrectStatusCode() { + void init_withConfig_setsCorrectStatusCode() { UrlPathValidator pathValidator = new UrlPathValidator(); Map params = new HashMap<>(); params.put(UrlPathValidator.PARAM_INVALID_STATUS_CODE, "401"); @@ -49,7 +46,7 @@ public void init_withConfig_setsCorrectStatusCode() { } @Test - public void init_withWrongConfig_setsDefaultStatusCode() { + void init_withWrongConfig_setsDefaultStatusCode() { UrlPathValidator pathValidator = new UrlPathValidator(); Map params = new HashMap<>(); params.put(UrlPathValidator.PARAM_INVALID_STATUS_CODE, "hello"); @@ -64,7 +61,7 @@ public void init_withWrongConfig_setsDefaultStatusCode() { } @Test - public void doFilter_invalidRelativePathUri_setsDefaultStatusCode() { + void doFilter_invalidRelativePathUri_setsDefaultStatusCode() { AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest(new AwsProxyRequestBuilder("../..", "GET").build(), null, null); AwsHttpServletResponse resp = new AwsHttpServletResponse(req, null); UrlPathValidator pathValidator = new UrlPathValidator(); @@ -79,7 +76,7 @@ public void doFilter_invalidRelativePathUri_setsDefaultStatusCode() { } @Test - public void doFilter_invalidUri_setsDefaultStatusCode() { + void doFilter_invalidUri_setsDefaultStatusCode() { AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest(new AwsProxyRequestBuilder("wonkyprotocol://˝Ó#\u0009", "GET").build(), null, null); AwsHttpServletResponse resp = new AwsHttpServletResponse(req, null); UrlPathValidator pathValidator = new UrlPathValidator(); 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/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java similarity index 86% rename from aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java rename to aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java index 998bf4748..e42130453 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/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java @@ -16,15 +16,17 @@ import com.amazonaws.serverless.proxy.model.*; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.io.IOUtils; -import org.apache.http.HttpEntity; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.ByteArrayBody; -import org.apache.http.entity.mime.content.StringBody; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.entity.mime.ByteArrayBody; +import org.apache.hc.client5.http.entity.mime.StringBody; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; import java.io.ByteArrayInputStream; import java.io.File; @@ -35,12 +37,6 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; 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; /** @@ -54,7 +50,7 @@ public class AwsProxyRequestBuilder { private AwsProxyRequest request; private MultipartEntityBuilder multipartBuilder; - + //------------------------------------------------------------- // Constructors //------------------------------------------------------------- @@ -75,7 +71,8 @@ public AwsProxyRequestBuilder(AwsProxyRequest req) { public AwsProxyRequestBuilder(String path, String httpMethod) { this.request = new AwsProxyRequest(); - this.request.setMultiValueHeaders(new Headers()); // avoid NPE + this.request.setMultiValueHeaders(new Headers());// avoid NPE + this.request.setHeaders(new SingleValueHeaders()); this.request.setHttpMethod(httpMethod); this.request.setPath(path); this.request.setMultiValueQueryStringParameters(new MultiValuedTreeMap<>()); @@ -90,22 +87,41 @@ public AwsProxyRequestBuilder(String path, String httpMethod) { this.request.getRequestContext().setIdentity(identity); } - - //------------------------------------------------------------- + //------------------------------------------------------------- // Methods - Public //------------------------------------------------------------- public AwsProxyRequestBuilder alb() { - this.request.setRequestContext(new AwsProxyRequestContext()); - this.request.getRequestContext().setElb(new AlbContext()); - this.request.getRequestContext().getElb().setTargetGroupArn( + /* + * This method sets up the requestContext to look like an ALB request and also + * re-encodes URL query params, since ALBs do not decode them. This now returns + * a new AwsProxyRequestBuilder with the new query param state, so the original + * builder maintains the original configured state and can be then be reused in + * further unit tests. For now the simplest way to accomplish a deep copy is by + * serializing to JSON then deserializing. + */ + + ObjectMapper objectMapper = new ObjectMapper(); + AwsProxyRequest albRequest = null; + try { + String json = objectMapper.writeValueAsString(this.request); + albRequest = objectMapper.readValue(json, AwsProxyRequest.class); + } catch (JsonProcessingException jpe) { + throw new RuntimeException(jpe); + } + + if (albRequest.getRequestContext() == null) { + albRequest.setRequestContext(new AwsProxyRequestContext()); + } + albRequest.getRequestContext().setElb(new AlbContext()); + albRequest.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) { + if (albRequest.getMultiValueQueryStringParameters() != null) { MultiValuedTreeMap newQs = new MultiValuedTreeMap<>(); - for (Map.Entry> e : request.getMultiValueQueryStringParameters().entrySet()) { + for (Map.Entry> e : albRequest.getMultiValueQueryStringParameters().entrySet()) { for (String v : e.getValue()) { try { // this is a terrible hack. In our Spring tests we use the comma as a control character for lists @@ -118,9 +134,9 @@ public AwsProxyRequestBuilder alb() { } } } - request.setMultiValueQueryStringParameters(newQs); + albRequest.setMultiValueQueryStringParameters(newQs); } - return this; + return new AwsProxyRequestBuilder(albRequest); } public AwsProxyRequestBuilder stage(String stageName) { @@ -146,6 +162,9 @@ public AwsProxyRequestBuilder json() { public AwsProxyRequestBuilder form(String key, String value) { + if (key == null || value == null) { + throw new IllegalArgumentException("form() does not support null key or value"); + } if (request.getMultiValueHeaders() == null) { request.setMultiValueHeaders(new Headers()); } @@ -154,7 +173,12 @@ public AwsProxyRequestBuilder form(String key, String value) { if (body == null) { body = ""; } - body += (body.equals("")?"":"&") + key + "=" + value; + // URL-encode key and value to form expected body of a form post + try { + body += (body.equals("") ? "" : "&") + URLEncoder.encode(key, "UTF-8") + "=" + URLEncoder.encode(value, "UTF-8"); + } catch (UnsupportedEncodingException ex) { + throw new RuntimeException("Could not encode form parameter: " + key + "=" + value, ex); + } request.setBody(body); return this; } @@ -168,7 +192,7 @@ public AwsProxyRequestBuilder formFilePart(String fieldName, String fileName, by return this; } - public AwsProxyRequestBuilder formFieldPart(String fieldName, String fieldValue) + public AwsProxyRequestBuilder formTextFieldPart(String fieldName, String fieldValue) throws IOException { if (request.getMultiValueHeaders() == null) { request.setMultiValueHeaders(new Headers()); @@ -176,7 +200,7 @@ public AwsProxyRequestBuilder formFieldPart(String fieldName, String fieldValue) if (multipartBuilder == null) { multipartBuilder = MultipartEntityBuilder.create(); } - multipartBuilder.addPart(fieldName, new StringBody(fieldValue)); + multipartBuilder.addPart(fieldName, new StringBody(fieldValue, ContentType.TEXT_PLAIN)); buildMultipartBody(); return this; } @@ -194,7 +218,7 @@ private void buildMultipartBody() request.setBody(Base64.getMimeEncoder().encodeToString(finalBuffer)); request.setIsBase64Encoded(true); this.request.setMultiValueHeaders(new Headers()); - header(HttpHeaders.CONTENT_TYPE, bodyEntity.getContentType().getValue()); + header(HttpHeaders.CONTENT_TYPE, bodyEntity.getContentType()); header(HttpHeaders.CONTENT_LENGTH, bodyEntity.getContentLength() + ""); } @@ -218,35 +242,15 @@ public AwsProxyRequestBuilder multiValueQueryString(MultiValuedTreeMap()); } - if (request.getRequestSource() == AwsProxyRequest.RequestSource.API_GATEWAY) { - this.request.getMultiValueQueryStringParameters().add(key, value); - } - // ALB does not decode parameters automatically like API Gateway. - if (request.getRequestSource() == AwsProxyRequest.RequestSource.ALB) { - try { - //if (URLDecoder.decode(value, ContainerConfig.DEFAULT_CONTENT_CHARSET).equals(value)) { - // TODO: Assume we are always given an unencoded value, smarter check here to encode - // only if necessary - this.request.getMultiValueQueryStringParameters().add( - key, - URLEncoder.encode(value, ContainerConfig.DEFAULT_CONTENT_CHARSET) - ); - //} - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - - } + this.request.getMultiValueQueryStringParameters().add(key, value); return this; } - public AwsProxyRequestBuilder body(String body) { this.request.setBody(body); return this; @@ -285,7 +289,7 @@ public AwsProxyRequestBuilder binaryBody(InputStream is) public AwsProxyRequestBuilder authorizerPrincipal(String principal) { - if (this.request.getRequestSource() == AwsProxyRequest.RequestSource.API_GATEWAY) { + if (this.request.getRequestSource() == RequestSource.API_GATEWAY) { if (this.request.getRequestContext().getAuthorizer() == null) { this.request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext()); } @@ -295,7 +299,7 @@ public AwsProxyRequestBuilder authorizerPrincipal(String principal) { } this.request.getRequestContext().getAuthorizer().getClaims().setSubject(principal); } - if (this.request.getRequestSource() == AwsProxyRequest.RequestSource.ALB) { + if (this.request.getRequestSource() == RequestSource.ALB) { header("x-amzn-oidc-identity", principal); try { header( @@ -479,11 +483,11 @@ public HttpApiV2ProxyRequest toHttpApiV2Request() { request.getMultiValueQueryStringParameters().forEach((k, v) -> { for (String s : v) { rawQueryString.append("&"); - rawQueryString.append(k); - rawQueryString.append("="); try { // 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(k, "UTF-8").replaceAll("%2C", ",")); + rawQueryString.append("="); rawQueryString.append(URLEncoder.encode(s, "UTF-8").replaceAll("%2C", ",")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilderTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilderTest.java new file mode 100644 index 000000000..b851e31dc --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilderTest.java @@ -0,0 +1,157 @@ +package com.amazonaws.serverless.proxy.internal.testutils; + +import java.io.IOException; +import java.net.URLEncoder; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; + +import com.amazonaws.serverless.proxy.model.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class AwsProxyRequestBuilderTest { + + private static final String TEST_KEY = "testkey"; + private static final String TEST_VALUE = "testvalue"; + private static final String TEST_KEY_FOR_ENCODING = "test@key 1"; + private static final String TEST_VALUE_FOR_ENCODING = "test value!!"; + + + void baseConstructorAsserts(AwsProxyRequest request) { + assertEquals(0, request.getMultiValueHeaders().size()); + assertEquals(0, request.getHeaders().size()); + assertEquals(0, request.getMultiValueQueryStringParameters().size()); + assertNotNull(request.getRequestContext()); + assertNotNull(request.getRequestContext().getRequestId()); + assertNotNull(request.getRequestContext().getExtendedRequestId()); + assertEquals("test", request.getRequestContext().getStage()); + assertEquals("HTTP/1.1", request.getRequestContext().getProtocol()); + assertNotNull(request.getRequestContext().getRequestTimeEpoch()); + assertNotNull(request.getRequestContext().getIdentity()); + assertEquals("127.0.0.1", request.getRequestContext().getIdentity().getSourceIp()); + } + + @Test + void constructor_path_httpMethod() { + + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder("/path", "GET"); + AwsProxyRequest request = builder.build(); + assertEquals("/path", request.getPath()); + assertEquals("GET", request.getHttpMethod()); + baseConstructorAsserts(request); + } + + @Test + void constructor_path_nullHttpMethod() { + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder("/path"); + AwsProxyRequest request = builder.build(); + assertNull(request.getHttpMethod()); + assertEquals("/path", request.getPath()); + baseConstructorAsserts(request); + } + + @Test + void constructor_nullPath_nullHttpMethod() { + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder(); + AwsProxyRequest request = builder.build(); + assertNull(request.getHttpMethod()); + assertNull(request.getPath()); + baseConstructorAsserts(request); + } + + @Test + void form_key_value() { + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder("/path", "POST"); + builder.form(TEST_KEY, TEST_VALUE); + AwsProxyRequest request = builder.build(); + assertEquals(1, request.getMultiValueHeaders().get(HttpHeaders.CONTENT_TYPE).size()); + assertEquals(MediaType.APPLICATION_FORM_URLENCODED, request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + assertNull(request.getHeaders().get(HttpHeaders.CONTENT_TYPE)); + assertNotNull(request.getBody()); + assertEquals(TEST_KEY + "=" + TEST_VALUE, request.getBody()); + } + + @Test + void form_key_nullKey_nullValue() { + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder("/path", "POST"); + assertThrows(IllegalArgumentException.class, () -> builder.form(null, TEST_VALUE)); + assertThrows(IllegalArgumentException.class, () -> builder.form(TEST_KEY, null)); + assertThrows(IllegalArgumentException.class, () -> builder.form(null, null)); + } + + @Test + void form_keyEncoded_valueEncoded() throws IOException { + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder("/path", "POST"); + builder.form(TEST_KEY_FOR_ENCODING, TEST_VALUE_FOR_ENCODING); + AwsProxyRequest request = builder.build(); + + assertEquals(1, request.getMultiValueHeaders().get(HttpHeaders.CONTENT_TYPE).size()); + assertEquals(MediaType.APPLICATION_FORM_URLENCODED, request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + assertNull(request.getHeaders().get(HttpHeaders.CONTENT_TYPE)); + assertNotNull(request.getBody()); + String expected = URLEncoder.encode(TEST_KEY_FOR_ENCODING, "UTF-8") + "=" + URLEncoder.encode(TEST_VALUE_FOR_ENCODING, "UTF-8"); + assertEquals(expected, request.getBody()); + } + + @Test + void queryString_key_value() { + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder("/path", "POST"); + builder.queryString(TEST_KEY, TEST_VALUE); + AwsProxyRequest request = builder.build(); + + assertNull(request.getQueryStringParameters()); + assertEquals(1, request.getMultiValueQueryStringParameters().size()); + assertEquals(TEST_KEY, request.getMultiValueQueryStringParameters().keySet().iterator().next()); + assertEquals(TEST_VALUE, request.getMultiValueQueryStringParameters().get(TEST_KEY).get(0)); + assertEquals(TEST_VALUE, request.getMultiValueQueryStringParameters().getFirst(TEST_KEY)); + } + + @Test + void queryString_keyNotEncoded_valueNotEncoded() { + // builder should not URL encode key or value for query string + // in the case of an ALB where values should be encoded, the builder alb() method will handle it + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder("/path", "POST"); + builder.queryString(TEST_KEY_FOR_ENCODING, TEST_VALUE_FOR_ENCODING); + AwsProxyRequest request = builder.build(); + + assertNull(request.getQueryStringParameters()); + assertEquals(1, request.getMultiValueQueryStringParameters().size()); + assertEquals(TEST_KEY_FOR_ENCODING, request.getMultiValueQueryStringParameters().keySet().iterator().next()); + assertEquals(TEST_VALUE_FOR_ENCODING, request.getMultiValueQueryStringParameters().get(TEST_KEY_FOR_ENCODING).get(0)); + assertEquals(TEST_VALUE_FOR_ENCODING, request.getMultiValueQueryStringParameters().getFirst(TEST_KEY_FOR_ENCODING)); + } + + @Test + void queryString_alb_key_value() { + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder("/path", "POST"); + builder.queryString(TEST_KEY, TEST_VALUE); + AwsProxyRequest request = builder.alb().build(); + + assertNull(request.getQueryStringParameters()); + assertEquals(1, request.getMultiValueQueryStringParameters().size()); + assertEquals(TEST_KEY, request.getMultiValueQueryStringParameters().keySet().iterator().next()); + assertEquals(TEST_VALUE, request.getMultiValueQueryStringParameters().get(TEST_KEY).get(0)); + assertEquals(TEST_VALUE, request.getMultiValueQueryStringParameters().getFirst(TEST_KEY)); + } + + @Test + void alb_keyEncoded_valueEncoded() throws IOException { + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder("/path", "POST"); + MultiValuedTreeMap map = new MultiValuedTreeMap<>(); + map.add(TEST_KEY_FOR_ENCODING, TEST_VALUE_FOR_ENCODING); + builder.multiValueQueryString(map); + AwsProxyRequest request = builder.alb().build(); + + String expectedKey = URLEncoder.encode(TEST_KEY_FOR_ENCODING, "UTF-8"); + String expectedValue = URLEncoder.encode(TEST_VALUE_FOR_ENCODING, "UTF-8"); + assertEquals(1, request.getMultiValueQueryStringParameters().size()); + assertEquals(expectedKey, request.getMultiValueQueryStringParameters().keySet().iterator().next()); + assertEquals(expectedValue, request.getMultiValueQueryStringParameters().get(expectedKey).get(0)); + assertEquals(expectedValue, request.getMultiValueQueryStringParameters().getFirst(expectedKey)); + assertEquals(expectedKey, request.getMultiValueQueryStringParameters().keySet().iterator().next()); + assertEquals(expectedValue, request.getMultiValueQueryStringParameters().get(expectedKey).get(0)); + assertEquals(expectedValue, request.getMultiValueQueryStringParameters().getFirst(expectedKey)); + } + +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/MockLambdaConsoleLogger.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/MockLambdaConsoleLogger.java similarity index 100% rename from aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/MockLambdaConsoleLogger.java rename to aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/MockLambdaConsoleLogger.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/MockLambdaContext.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/MockLambdaContext.java similarity index 100% rename from aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/MockLambdaContext.java rename to aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/MockLambdaContext.java diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/MockServlet.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/MockServlet.java new file mode 100644 index 000000000..6dd2ade66 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/MockServlet.java @@ -0,0 +1,23 @@ +package com.amazonaws.serverless.proxy.internal.testutils; + +import java.io.IOException; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class MockServlet extends HttpServlet { + + private int serviceCalls = 0; + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + super.service(req, resp); + serviceCalls++; + } + + public int getServiceCalls() { + return serviceCalls; + } +} diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/ApiGatewayAuthorizerContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/ApiGatewayAuthorizerContextTest.java index 87ab0ed7a..196552c1d 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/ApiGatewayAuthorizerContextTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/ApiGatewayAuthorizerContextTest.java @@ -2,11 +2,11 @@ import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class ApiGatewayAuthorizerContextTest { private static final String FIELD_NAME_1 = "CUSTOM_FIELD_1"; @@ -60,7 +60,7 @@ public class ApiGatewayAuthorizerContextTest { + "}"; @Test - public void authorizerContext_serialize_customValues() { + void authorizerContext_serialize_customValues() { try { AwsProxyRequest req = new AwsProxyRequestBuilder().fromJsonString(AUTHORIZER_REQUEST).build(); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java index b06e04cf6..6d0aa9eeb 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java @@ -1,13 +1,11 @@ package com.amazonaws.serverless.proxy.model; -import static junit.framework.TestCase.assertTrue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; + import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.fasterxml.jackson.databind.ObjectMapper; @@ -16,7 +14,7 @@ public class AwsProxyRequestTest { private static final String CUSTOM_HEADER_VALUE = "123456"; @Test - public void deserialize_multiValuedHeaders_caseInsensitive() throws IOException { + void deserialize_multiValuedHeaders_caseInsensitive() throws IOException { AwsProxyRequest req = new AwsProxyRequestBuilder() .fromJsonString(getRequestJson(true, CUSTOM_HEADER_KEY_LOWER_CASE, CUSTOM_HEADER_VALUE)).build(); assertNotNull(req.getMultiValueHeaders().get(CUSTOM_HEADER_KEY_LOWER_CASE.toUpperCase())); @@ -25,7 +23,7 @@ public void deserialize_multiValuedHeaders_caseInsensitive() throws IOException } @Test - public void deserialize_base64Encoded_readsBoolCorrectly() throws IOException { + void deserialize_base64Encoded_readsBoolCorrectly() throws IOException { AwsProxyRequest req = new AwsProxyRequestBuilder() .fromJsonString(getRequestJson(true, CUSTOM_HEADER_KEY_LOWER_CASE, CUSTOM_HEADER_VALUE)).build(); assertTrue(req.isBase64Encoded()); @@ -35,7 +33,7 @@ public void deserialize_base64Encoded_readsBoolCorrectly() throws IOException { } @Test - public void serialize_base64Encoded_fieldContainsIsPrefix() throws IOException { + void serialize_base64Encoded_fieldContainsIsPrefix() throws IOException { AwsProxyRequest req = new AwsProxyRequestBuilder() .fromJsonString(getRequestJson(true, CUSTOM_HEADER_KEY_LOWER_CASE, CUSTOM_HEADER_VALUE)).build(); ObjectMapper mapper = new ObjectMapper(); @@ -106,9 +104,9 @@ private String getRequestJson(boolean base64Encoded, String headerKey, String he } @Test - public void deserialize_singleValuedHeaders() throws IOException { + void deserialize_singleValuedHeaders() throws IOException { AwsProxyRequest req = - new AwsProxyRequestBuilder().fromJsonString(getSingleValueRequestJson()).build(); + new AwsProxyRequestBuilder().fromJsonString(getSingleValueRequestJson()).build(); assertThat(req.getHeaders().get("accept"), is("*")); } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/CognitoAuthorizerClaimsTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/CognitoAuthorizerClaimsTest.java index 55b5d936f..1ad2314b6 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/CognitoAuthorizerClaimsTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/CognitoAuthorizerClaimsTest.java @@ -2,14 +2,14 @@ import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Locale; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class CognitoAuthorizerClaimsTest { @@ -77,7 +77,7 @@ public class CognitoAuthorizerClaimsTest { @Test - public void claims_serialize_validJsonString() { + void claims_serialize_validJsonString() { try { AwsProxyRequest req = new AwsProxyRequestBuilder().fromJsonString(USER_POOLS_REQUEST).build(); @@ -90,7 +90,7 @@ public void claims_serialize_validJsonString() { } @Test - public void claims_dateParse_issueTime() { + void claims_dateParse_issueTime() { try { AwsProxyRequest req = new AwsProxyRequestBuilder().fromJsonString(USER_POOLS_REQUEST).build(); 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 c673a59e1..20ff4dff2 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 @@ -2,11 +2,11 @@ import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.fasterxml.jackson.core.JsonProcessingException; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class HttpApiV2ProxyRequestTest { @@ -128,14 +128,59 @@ public class HttpApiV2ProxyRequestTest { " \"body\": \"Hello from Lambda\",\n" + " \"isBase64Encoded\": false,\n" + " \"stageVariables\": {\"stageVariable1\": \"value1\", \"stageVariable2\": \"value2\"}\n" + - " }\n";; + " }\n"; + private static final String IAM_AUTHORIZER = "{\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\": { \"iam\": {\n" + + " \"accessKey\": \"AKIAIOSFODNN7EXAMPLE\",\n" + + " \"accountId\": \"123456789012\",\n" + + " \"callerId\": \"AIDACKCEVSQ6C2EXAMPLE\",\n" + + " \"cognitoIdentity\": null,\n" + + " \"principalOrgId\": \"AIDACKCEVSQORGEXAMPLE\",\n" + + " \"userArn\": \"arn:aws:iam::111122223333:user/example-user\",\n" + + " \"userId\": \"AIDACOSFODNN7EXAMPLE2\"\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"; @Test - public void deserialize_fromJsonString_authorizerPopulatedCorrectly() { + void deserialize_fromJsonString_authorizerPopulatedCorrectly() { try { - HttpApiV2ProxyRequest req = LambdaContainerHandler.getObjectMapper().readValue(BASE_PROXY_REQUEST, HttpApiV2ProxyRequest.class); + 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()); + assertEquals(RequestSource.API_GATEWAY, req.getRequestSource()); } catch (JsonProcessingException e) { e.printStackTrace(); fail("Exception while parsing request" + e.getMessage()); @@ -143,12 +188,14 @@ public void deserialize_fromJsonString_authorizerPopulatedCorrectly() { } @Test - public void deserialize_fromJsonString_authorizerEmptyMap() { + void deserialize_fromJsonString_authorizerEmptyMap() { try { - HttpApiV2ProxyRequest req = LambdaContainerHandler.getObjectMapper().readValue(NO_AUTH_PROXY, HttpApiV2ProxyRequest.class); + HttpApiV2ProxyRequest req = LambdaContainerHandler.getObjectMapper().readValue(NO_AUTH_PROXY, + HttpApiV2ProxyRequest.class); assertNotNull(req.getRequestContext().getAuthorizer()); assertFalse(req.getRequestContext().getAuthorizer().isJwt()); assertFalse(req.getRequestContext().getAuthorizer().isLambda()); + assertFalse(req.getRequestContext().getAuthorizer().isIam()); } catch (JsonProcessingException e) { e.printStackTrace(); fail("Exception while parsing request" + e.getMessage()); @@ -156,9 +203,10 @@ public void deserialize_fromJsonString_authorizerEmptyMap() { } @Test - public void deserialize_fromJsonString_lambdaAuthorizer() { + void deserialize_fromJsonString_lambdaAuthorizer() { try { - HttpApiV2ProxyRequest req = LambdaContainerHandler.getObjectMapper().readValue(LAMBDA_AUTHORIZER, HttpApiV2ProxyRequest.class); + HttpApiV2ProxyRequest req = LambdaContainerHandler.getObjectMapper().readValue(LAMBDA_AUTHORIZER, + HttpApiV2ProxyRequest.class); assertNotNull(req.getRequestContext().getAuthorizer()); assertFalse(req.getRequestContext().getAuthorizer().isJwt()); assertTrue(req.getRequestContext().getAuthorizer().isLambda()); @@ -171,12 +219,41 @@ public void deserialize_fromJsonString_lambdaAuthorizer() { } @Test - public void deserialize_fromJsonString_isBase64EncodedPopulates() { + void deserialize_fromJsonString_iamAuthorizer() { + try { + HttpApiV2ProxyRequest req = LambdaContainerHandler.getObjectMapper().readValue(IAM_AUTHORIZER, + HttpApiV2ProxyRequest.class); + assertNotNull(req.getRequestContext().getAuthorizer()); + assertFalse(req.getRequestContext().getAuthorizer().isJwt()); + assertFalse(req.getRequestContext().getAuthorizer().isLambda()); + assertTrue(req.getRequestContext().getAuthorizer().isIam()); + assertEquals("AKIAIOSFODNN7EXAMPLE", + req.getRequestContext().getAuthorizer().getIamAuthorizer().getAccessKey()); + assertEquals("123456789012", req.getRequestContext().getAuthorizer().getIamAuthorizer().getAccountId()); + assertEquals("AIDACKCEVSQ6C2EXAMPLE", + req.getRequestContext().getAuthorizer().getIamAuthorizer().getCallerId()); + assertNull(req.getRequestContext().getAuthorizer().getIamAuthorizer().getCognitoIdentity()); + assertEquals("AIDACKCEVSQORGEXAMPLE", + req.getRequestContext().getAuthorizer().getIamAuthorizer().getPrincipalOrgId()); + assertEquals("arn:aws:iam::111122223333:user/example-user", + req.getRequestContext().getAuthorizer().getIamAuthorizer().getUserArn()); + assertEquals("AIDACOSFODNN7EXAMPLE2", + req.getRequestContext().getAuthorizer().getIamAuthorizer().getUserId()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + fail("Exception while parsing request" + e.getMessage()); + } + } + + @Test + void deserialize_fromJsonString_isBase64EncodedPopulates() { try { - HttpApiV2ProxyRequest req = LambdaContainerHandler.getObjectMapper().readValue(BASE_PROXY_REQUEST, HttpApiV2ProxyRequest.class); + 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()); + assertEquals(RequestSource.API_GATEWAY, req.getRequestSource()); } catch (JsonProcessingException e) { e.printStackTrace(); fail("Exception while parsing request" + e.getMessage()); @@ -184,7 +261,7 @@ public void deserialize_fromJsonString_isBase64EncodedPopulates() { } @Test - public void serialize_toJsonString_authorizerPopulatesCorrectly() { + void serialize_toJsonString_authorizerPopulatesCorrectly() { HttpApiV2ProxyRequest req = new HttpApiV2ProxyRequest(); req.setBase64Encoded(false); req.setRequestContext(new HttpApiV2ProxyRequestContext()); @@ -205,4 +282,4 @@ public void serialize_toJsonString_authorizerPopulatesCorrectly() { fail("Exception while serializing request" + e.getMessage()); } } -} +} \ No newline at end of file diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMapTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMapTest.java index edc6d2810..c2566cbe0 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMapTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMapTest.java @@ -1,17 +1,15 @@ package com.amazonaws.serverless.proxy.model; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.*; public class MultiValuedTreeMapTest { @Test - public void add_sameNameCaseSensitive_expectBothValues() { + void add_sameNameCaseSensitive_expectBothValues() { MultiValuedTreeMap map = new MultiValuedTreeMap<>(); map.add("Test", "test"); map.add("Test", "test2"); @@ -28,7 +26,7 @@ public void add_sameNameCaseSensitive_expectBothValues() { } @Test - public void add_sameNameCaseInsensitive_expectOneValue() { + void add_sameNameCaseInsensitive_expectOneValue() { Headers map = new Headers(); map.add("Test", "test"); assertNotNull(map.get("Test")); @@ -41,7 +39,7 @@ public void add_sameNameCaseInsensitive_expectOneValue() { } @Test - public void addFirst_sameNameKey_ExpectFirstReplaced() { + void addFirst_sameNameKey_ExpectFirstReplaced() { MultiValuedTreeMap map = new MultiValuedTreeMap<>(); map.add("Test", "test1"); map.add("Test", "test2"); diff --git a/aws-serverless-java-container-jersey/pom.xml b/aws-serverless-java-container-jersey/pom.xml index 11c9ee9ca..1788a36b8 100644 --- a/aws-serverless-java-container-jersey/pom.xml +++ b/aws-serverless-java-container-jersey/pom.xml @@ -6,17 +6,17 @@ AWS Serverless Java container support - Jersey implementation Allows Java applications written for Jersey to run in AWS Lambda https://aws.amazon.com/lambda - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT com.amazonaws.serverless aws-serverless-java-container - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT .. - 2.37 + 3.1.10 @@ -24,7 +24,7 @@ com.amazonaws.serverless aws-serverless-java-container-core - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT com.fasterxml.jackson.core @@ -32,8 +32,15 @@ + + com.amazonaws.serverless + aws-serverless-java-container-core + 2.1.5-SNAPSHOT + tests + test-jar + test + - org.glassfish.jersey.core jersey-server @@ -54,15 +61,13 @@ - commons-codec commons-codec - 1.15 + 1.18.0 test - com.fasterxml.jackson.core jackson-databind @@ -71,7 +76,6 @@ test - org.glassfish.jersey.media jersey-media-json-jackson @@ -100,6 +104,17 @@ ${jersey.version} test + + org.junit.jupiter + junit-jupiter + test + + + jakarta.ws.rs + jakarta.ws.rs-api + 3.1.0 + + @@ -151,9 +166,8 @@ org.apache.maven.plugins maven-surefire-plugin - 2.9 - always + false @@ -184,13 +198,6 @@ 7 false - - - - check - - - 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 27e3157f0..79d030bda 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 @@ -26,17 +26,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.InternalServerErrorException; -import javax.ws.rs.core.Application; -import javax.ws.rs.core.SecurityContext; -import javax.ws.rs.core.UriBuilder; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.InternalServerErrorException; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.UriBuilder; import java.io.IOException; import java.io.InputStream; 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 fe50cd0fd..43bd7eac1 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 @@ -30,14 +30,13 @@ import org.glassfish.jersey.process.internal.RequestScoped; import org.glassfish.jersey.server.ResourceConfig; -import javax.servlet.DispatcherType; -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; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterRegistration; +import jakarta.servlet.Servlet; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.core.Application; import java.util.EnumSet; import java.util.concurrent.CountDownLatch; @@ -208,7 +207,7 @@ protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request public void initialize() { Timer.start("JERSEY_COLD_START_INIT"); - // manually add the spark filter to the chain. This should the last one and match all uris + // manually add the filter to the chain. This should the last one and match all uris FilterRegistration.Dynamic jerseyFilterReg = getServletContext().addFilter("JerseyFilter", jerseyFilter); jerseyFilterReg.addMappingForUrlPatterns( EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.INCLUDE, DispatcherType.FORWARD), diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyServletResponseWriter.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyServletResponseWriter.java index 938c6f9ff..870390f80 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyServletResponseWriter.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyServletResponseWriter.java @@ -22,9 +22,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.InternalServerErrorException; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.InternalServerErrorException; import java.io.IOException; import java.io.OutputStream; diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletContextSupplier.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletContextSupplier.java index 78f8c5c5a..4d93a89ab 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletContextSupplier.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletContextSupplier.java @@ -15,10 +15,10 @@ import org.glassfish.jersey.server.ContainerRequest; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.InternalServerErrorException; -import javax.ws.rs.core.Context; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.InternalServerErrorException; +import jakarta.ws.rs.core.Context; import java.util.function.Supplier; diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletRequestSupplier.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletRequestSupplier.java index fe1b21f13..c3a4653bf 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletRequestSupplier.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletRequestSupplier.java @@ -15,8 +15,8 @@ import org.glassfish.jersey.server.ContainerRequest; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.core.Context; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.Context; import java.util.function.Supplier; diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletResponseSupplier.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletResponseSupplier.java index 1fb6dc175..d4123f22e 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletResponseSupplier.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/suppliers/AwsProxyServletResponseSupplier.java @@ -15,8 +15,8 @@ import org.glassfish.jersey.server.ContainerRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.Context; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.core.Context; import java.util.function.Supplier; diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java index 5da93ca08..d05c6814f 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java @@ -13,41 +13,34 @@ package com.amazonaws.serverless.proxy.jersey; import com.amazonaws.serverless.proxy.RequestReader; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; import com.amazonaws.serverless.proxy.jersey.providers.ServletRequestFilter; import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; import com.amazonaws.serverless.proxy.jersey.model.MapResponseModel; import com.amazonaws.serverless.proxy.jersey.model.SingleValueModel; -import org.apache.commons.io.IOUtils; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; -import org.glassfish.jersey.media.multipart.FormDataMultiPart; import org.glassfish.jersey.media.multipart.FormDataParam; -import javax.inject.Inject; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.*; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.SecurityContext; -import javax.ws.rs.core.UriInfo; - -import java.io.ByteArrayInputStream; +import jakarta.inject.Inject; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.*; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.UriInfo; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; -import java.util.HashMap; import java.util.List; import java.util.Random; -import static com.amazonaws.serverless.proxy.jersey.JerseyHandlerFilter.JERSEY_SERVLET_REQUEST_PROPERTY; - /** * Jersey application class for aws-serverless-java-container unit proxy 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 dbcaac407..b845a0b69 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 @@ -30,28 +30,26 @@ import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.server.ResourceConfig; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.UUID; -import static org.junit.Assert.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; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; /** * Unit test class for the Jersey AWS_PROXY default implementation */ -@RunWith(Parameterized.class) public class JerseyAwsProxyTest { private static final String CUSTOM_HEADER_KEY = "x-custom-header"; private static final String CUSTOM_HEADER_VALUE = "my-custom-value"; @@ -62,43 +60,42 @@ public class JerseyAwsProxyTest { private static ObjectMapper objectMapper = new ObjectMapper(); private static ResourceConfig app = new ResourceConfig().packages("com.amazonaws.serverless.proxy.jersey") - .register(LoggingFeature.class) - .register(ServletRequestFilter.class) - .register(MultiPartFeature.class) - .register(new ResourceBinder()) - .property(LoggingFeature.LOGGING_FEATURE_VERBOSITY_SERVER, LoggingFeature.Verbosity.PAYLOAD_ANY); + .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 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); + .register(LoggingFeature.class) + .register(ServletRequestFilter.class) + .register(MultiPartFeature.class) + .register(new ResourceBinder()) + .property(LoggingFeature.LOGGING_FEATURE_VERBOSITY_SERVER, LoggingFeature.Verbosity.PAYLOAD_ANY); private static ResourceConfig appWithoutRegisteredDependencies = new ResourceConfig() - .packages("com.amazonaws.serverless.proxy.jersey") - .register(LoggingFeature.class) - .register(ServletRequestFilter.class) - .register(MultiPartFeature.class) - .property(LoggingFeature.LOGGING_FEATURE_VERBOSITY_SERVER, LoggingFeature.Verbosity.PAYLOAD_ANY); + .packages("com.amazonaws.serverless.proxy.jersey") + .register(LoggingFeature.class) + .register(ServletRequestFilter.class) + .register(MultiPartFeature.class) + .property(LoggingFeature.LOGGING_FEATURE_VERBOSITY_SERVER, LoggingFeature.Verbosity.PAYLOAD_ANY); private static JerseyLambdaContainerHandler handler; private static JerseyLambdaContainerHandler httpApiHandler; private static JerseyLambdaContainerHandler handlerWithoutRegisteredDependencies - = JerseyLambdaContainerHandler.getAwsProxyHandler(appWithoutRegisteredDependencies); + = JerseyLambdaContainerHandler.getAwsProxyHandler(appWithoutRegisteredDependencies); private static Context lambdaContext = new MockLambdaContext(); private String type; - public JerseyAwsProxyTest(String reqType) { + public void initJerseyAwsProxyTest(String reqType) { type = reqType; } - @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); + return Arrays.asList(new Object[]{"API_GW", "ALB", "HTTP_API"}); } private AwsProxyRequestBuilder getRequestBuilder(String path, String method) { @@ -140,11 +137,13 @@ private JerseyLambdaContainerHandler getHandler() { } } - @Test - public void headers_getHeaders_echo() { + @MethodSource("data") + @ParameterizedTest + void headers_getHeaders_echo(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/headers", "GET") - .json() - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); + .json() + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -153,11 +152,13 @@ public void headers_getHeaders_echo() { validateMapResponseModel(output); } - @Test - public void headers_servletRequest_echo() { + @MethodSource("data") + @ParameterizedTest + void headers_servletRequest_echo(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/servlet-headers", "GET") - .json() - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); + .json() + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -166,31 +167,37 @@ public void headers_servletRequest_echo() { validateMapResponseModel(output); } - @Test - public void headers_servletRequest_failedDependencyInjection_expectInternalServerError() { + @MethodSource("data") + @ParameterizedTest + void headers_servletRequest_failedDependencyInjection_expectInternalServerError(String reqType) { + initJerseyAwsProxyTest(reqType); assumeTrue("API_GW".equals(type)); AwsProxyRequest request = getRequestBuilder("/echo/servlet-headers", "GET") - .json() - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .build(); + .json() + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .build(); AwsProxyResponse output = handlerWithoutRegisteredDependencies.proxy(request, lambdaContext); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), output.getStatusCode()); } - @Test - public void context_servletResponse_setCustomHeader() { + @MethodSource("data") + @ParameterizedTest + void context_servletResponse_setCustomHeader(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/servlet-response", "GET") - .json(); + .json(); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertTrue(output.getMultiValueHeaders().containsKey(EchoJerseyResource.SERVLET_RESP_HEADER_KEY)); } - @Test - public void context_serverInfo_correctContext() { + @MethodSource("data") + @ParameterizedTest + void context_serverInfo_correctContext(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/servlet-context", "GET"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -199,10 +206,12 @@ public void context_serverInfo_correctContext() { validateSingleValueModel(output, AwsServletContext.SERVER_INFO); } - @Test - public void requestScheme_valid_expectHttps() { + @MethodSource("data") + @ParameterizedTest + void requestScheme_valid_expectHttps(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/scheme", "GET") - .json(); + .json(); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -211,10 +220,12 @@ public void requestScheme_valid_expectHttps() { validateSingleValueModel(output, "https"); } - @Test - public void requestFilter_injectsServletRequest_expectCustomAttribute() { + @MethodSource("data") + @ParameterizedTest + void requestFilter_injectsServletRequest_expectCustomAttribute(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/filter-attribute", "GET") - .json(); + .json(); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -223,12 +234,14 @@ public void requestFilter_injectsServletRequest_expectCustomAttribute() { validateSingleValueModel(output, ServletRequestFilter.FILTER_ATTRIBUTE_VALUE); } - @Test - public void authorizer_securityContext_customPrincipalSuccess() { + @MethodSource("data") + @ParameterizedTest + void authorizer_securityContext_customPrincipalSuccess(String reqType) { + initJerseyAwsProxyTest(reqType); 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); + .json() + .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -236,14 +249,16 @@ public void authorizer_securityContext_customPrincipalSuccess() { validateSingleValueModel(output, AUTHORIZER_PRINCIPAL_ID); } - @Test - public void authorizer_securityContext_customAuthorizerContextSuccess() { + @MethodSource("data") + @ParameterizedTest + void authorizer_securityContext_customAuthorizerContextSuccess(String reqType) { + initJerseyAwsProxyTest(reqType); 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); + .json() + .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID) + .authorizerContextValue(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .queryString("key", CUSTOM_HEADER_KEY); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -252,41 +267,49 @@ public void authorizer_securityContext_customAuthorizerContextSuccess() { validateSingleValueModel(output, CUSTOM_HEADER_VALUE); } - @Test - public void errors_unknownRoute_expect404() { + @MethodSource("data") + @ParameterizedTest + void errors_unknownRoute_expect404(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/test33", "GET"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(404, output.getStatusCode()); } - @Test - public void error_contentType_invalidContentType() { + @MethodSource("data") + @ParameterizedTest + void error_contentType_invalidContentType(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/json-body", "POST") - .header("Content-Type", "application/octet-stream") - .body("asdasdasd"); + .header("Content-Type", "application/octet-stream") + .body("asdasdasd"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(415, output.getStatusCode()); } - @Test - public void error_statusCode_methodNotAllowed() { + @MethodSource("data") + @ParameterizedTest + void error_statusCode_methodNotAllowed(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/status-code", "POST") - .json() - .queryString("status", "201"); + .json() + .queryString("status", "201"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(405, output.getStatusCode()); } - @Test - public void responseBody_responseWriter_validBody() throws JsonProcessingException { + @MethodSource("data") + @ParameterizedTest + void responseBody_responseWriter_validBody(String reqType) throws JsonProcessingException { + initJerseyAwsProxyTest(reqType); SingleValueModel singleValueModel = new SingleValueModel(); singleValueModel.setValue(CUSTOM_HEADER_VALUE); AwsProxyRequestBuilder request = getRequestBuilder("/echo/json-body", "POST") - .json() - .body(objectMapper.writeValueAsString(singleValueModel)); + .json() + .body(objectMapper.writeValueAsString(singleValueModel)); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -295,18 +318,22 @@ public void responseBody_responseWriter_validBody() throws JsonProcessingExcepti validateSingleValueModel(output, CUSTOM_HEADER_VALUE); } - @Test - public void statusCode_responseStatusCode_customStatusCode() { + @MethodSource("data") + @ParameterizedTest + void statusCode_responseStatusCode_customStatusCode(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/status-code", "GET") - .json() - .queryString("status", "201"); + .json() + .queryString("status", "201"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(201, output.getStatusCode()); } - @Test - public void base64_binaryResponse_base64Encoding() { + @MethodSource("data") + @ParameterizedTest + void base64_binaryResponse_base64Encoding(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/binary", "GET"); AwsProxyResponse response = executeRequest(request, lambdaContext); @@ -314,8 +341,10 @@ public void base64_binaryResponse_base64Encoding() { assertTrue(Base64.isBase64(response.getBody())); } - @Test - public void exception_mapException_mapToNotImplemented() { + @MethodSource("data") + @ParameterizedTest + void exception_mapException_mapToNotImplemented(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/exception", "GET"); AwsProxyResponse response = executeRequest(request, lambdaContext); @@ -324,24 +353,28 @@ public void exception_mapException_mapToNotImplemented() { assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), response.getStatusCode()); } - @Test - public void stripBasePath_route_shouldRouteCorrectly() { + @MethodSource("data") + @ParameterizedTest + void stripBasePath_route_shouldRouteCorrectly(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/custompath/echo/status-code", "GET") - .json() - .queryString("status", "201"); + .json() + .queryString("status", "201"); getHandler().stripBasePath("/custompath"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(201, output.getStatusCode()); getHandler().stripBasePath(""); } - @Test - public void stripBasePath_route_shouldReturn404WithStageAsContext() { + @MethodSource("data") + @ParameterizedTest + void stripBasePath_route_shouldReturn404WithStageAsContext(String reqType) { + initJerseyAwsProxyTest(reqType); assumeTrue(!"ALB".equals(type)); AwsProxyRequestBuilder request = getRequestBuilder("/custompath/echo/status-code", "GET") - .stage("prod") - .json() - .queryString("status", "201"); + .stage("prod") + .json() + .queryString("status", "201"); getHandler().stripBasePath("/custompath"); LambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(true); AwsProxyResponse output = executeRequest(request, lambdaContext); @@ -350,57 +383,67 @@ public void stripBasePath_route_shouldReturn404WithStageAsContext() { LambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(false); } - @Test - public void stripBasePath_route_shouldReturn404() { + @MethodSource("data") + @ParameterizedTest + void stripBasePath_route_shouldReturn404(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/custompath/echo/status-code", "GET") - .json() - .queryString("status", "201"); + .json() + .queryString("status", "201"); getHandler().stripBasePath("/custom"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(404, output.getStatusCode()); getHandler().stripBasePath(""); } - @Test - public void securityContext_injectPrincipal_expectPrincipalName() { + @MethodSource("data") + @ParameterizedTest + void securityContext_injectPrincipal_expectPrincipalName(String reqType) { + initJerseyAwsProxyTest(reqType); assumeTrue("API_GW".equals(type)); AwsProxyRequestBuilder request = getRequestBuilder("/echo/security-context", "GET") - .authorizerPrincipal(USER_PRINCIPAL); + .authorizerPrincipal(USER_PRINCIPAL); AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, USER_PRINCIPAL); } - @Test - public void emptyStream_putNullBody_expectPutToSucceed() { + @MethodSource("data") + @ParameterizedTest + void emptyStream_putNullBody_expectPutToSucceed(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/empty-stream/" + CUSTOM_HEADER_KEY + "/test/2", "PUT") - .nullBody() - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); + .nullBody() + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, CUSTOM_HEADER_KEY); } - @Test - public void refererHeader_headerParam_expectCorrectInjection() { + @MethodSource("data") + @ParameterizedTest + void refererHeader_headerParam_expectCorrectInjection(String reqType) { + initJerseyAwsProxyTest(reqType); String refererValue = "test-referer"; AwsProxyRequestBuilder request = getRequestBuilder("/echo/referer-header", "GET") - .nullBody() - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .header("Referer", refererValue); + .nullBody() + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .header("Referer", refererValue); AwsProxyResponse resp = executeRequest(request, lambdaContext); assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, refererValue); } - @Test - public void textPlainContent_plain_responseHonorsContentType() { + @MethodSource("data") + @ParameterizedTest + void textPlainContent_plain_responseHonorsContentType(String reqType) { + initJerseyAwsProxyTest(reqType); AwsProxyRequestBuilder req = getRequestBuilder("/echo/plain", "GET") - .nullBody() - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN); + .nullBody() + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN); AwsProxyResponse resp = executeRequest(req, lambdaContext); assertEquals(200, resp.getStatusCode()); diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java index e8e86270b..e253f34a3 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java @@ -12,16 +12,15 @@ */ package com.amazonaws.serverless.proxy.jersey; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; -import javax.inject.Singleton; +import jakarta.inject.Singleton; import org.glassfish.jersey.internal.inject.AbstractBinder; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.server.ResourceConfig; -import org.junit.Test; - +import org.junit.jupiter.api.Test; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; @@ -47,7 +46,7 @@ protected void configure() { app); @Test - public void can_get_injected_resources() throws Exception { + void can_get_injected_resources() throws Exception { JerseyInjectionTest instance1 = handler.getInjectionManager().getInstance(JerseyInjectionTest.class); assertNotNull(instance1); 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 e736cbc1e..9dc1ab32a 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 @@ -14,12 +14,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.server.ResourceConfig; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MediaType; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -27,12 +26,11 @@ import java.util.Arrays; import java.util.Collection; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; -@RunWith(Parameterized.class) public class JerseyParamEncodingTest { private static final String SIMPLE_ENCODED_PARAM = "p/z+3"; @@ -40,55 +38,54 @@ public class JerseyParamEncodingTest { private static final String QUERY_STRING_KEY = "identifier"; private static final String QUERY_STRING_NON_ENCODED_VALUE = "Space Test"; private static final String QUERY_STRING_ENCODED_VALUE = "Space%20Test"; - private static final byte[] FILE_CONTENTS = new byte[] { - (byte)47, (byte)85, (byte)135, (byte)12, (byte)53, (byte)7, (byte)158, (byte)212, (byte)55, (byte)193, (byte)149, (byte)3, (byte)166, (byte)181, - (byte)151, (byte)84, (byte)122, (byte)200, (byte)244, (byte)5, (byte)115, (byte)159, (byte)66, (byte)64, (byte)143, (byte)211, (byte)13, (byte)63, - (byte)235, (byte)184, (byte)51, (byte)49, (byte)143, (byte)167, (byte)231, (byte)31, (byte)78, (byte)234, (byte)145, (byte)105, (byte)190, (byte)170, - (byte)49, (byte)135, (byte)175, (byte)106, (byte)25, (byte)86, (byte)145, (byte)181, (byte)156, (byte)23, (byte)153, (byte)99, (byte)175, (byte)63, - (byte)43, (byte)208, (byte)5, (byte)16, (byte)140, (byte)103, (byte)146, (byte)254, (byte)155, (byte)97, (byte)53, (byte)100, (byte)137, (byte)6, - (byte)62, (byte)101, (byte)189, (byte)137, (byte)140, (byte)5, (byte)110, (byte)218, (byte)113, (byte)132, (byte)36, (byte)188, (byte)19, (byte)168, - (byte)93, (byte)169, (byte)124, (byte)253, (byte)201, (byte)233, (byte)21, (byte)80, (byte)4, (byte)56, (byte)0, (byte)204, (byte)205, (byte)232, - (byte)213, (byte)253, (byte)232, (byte)91, (byte)153, (byte)169, (byte)82, (byte)247, (byte)78, (byte)71, (byte)188, (byte)71, (byte)23, (byte)171, - (byte)232, (byte)26, (byte)146, (byte)189, (byte)145, (byte)82, (byte)79, (byte)148, (byte)1, (byte)201, (byte)243, (byte)73, (byte)98, (byte)65, - (byte)236, (byte)177, (byte)211, (byte)106, (byte)105, (byte)46, (byte)204, (byte)214, (byte)55, (byte)182, (byte)55, (byte)149, (byte)221, (byte)52, - (byte)186, (byte)122, (byte)255, (byte)195, (byte)60, (byte)146, (byte)21, (byte)212, (byte)139, (byte)38, (byte)146, (byte)166, (byte)14, (byte)174, - (byte)242, (byte)145, (byte)16, (byte)44, (byte)68, (byte)89, (byte)25, (byte)219, (byte)62, (byte)227, (byte)6, (byte)89, (byte)194, (byte)146, (byte)93, - (byte)167, (byte)230, (byte)90, (byte)59, (byte)35, (byte)136, (byte)37, (byte)196, (byte)118, (byte)16, (byte)28, (byte)107, (byte)105, (byte)87, - (byte)195, (byte)86, (byte)87, (byte)180, (byte)176, (byte)118, (byte)6, (byte)29, (byte)26, (byte)51, (byte)94, (byte)21, (byte)23, (byte)32, (byte)156, - (byte)150, (byte)204, (byte)53, (byte)110, (byte)134, (byte)153, (byte)138, (byte)247, (byte)98, (byte)135, (byte)249, (byte)119, (byte)121, (byte)2, - (byte)42, (byte)62, (byte)198, (byte)197, (byte)112, (byte)153, (byte)244, (byte)174, (byte)145, (byte)54, (byte)246, (byte)44, (byte)198, (byte)50, - (byte)2, (byte)37, (byte)102, (byte)50, (byte)103, (byte)207, (byte)81, (byte)62, (byte)138, (byte)164, (byte)140, (byte)64, (byte)247, (byte)115, - (byte)40, (byte)41, (byte)252, (byte)54, (byte)189, (byte)207, (byte)124, (byte)147, (byte)122, (byte)243, (byte)83, (byte)34, (byte)160, (byte)64, (byte)189, (byte)226, (byte)202, (byte)181, (byte)55, (byte)158, (byte)121, (byte)78, (byte)143, (byte)41, (byte)58, (byte)27, (byte)77, (byte)186, (byte)214, (byte)23, (byte)132, (byte)100, (byte)180, (byte)26, (byte)37, (byte)247, (byte)254, (byte)97, (byte)214, (byte)57, (byte)30, (byte)46, (byte)96, (byte)44, (byte)138, (byte)15, (byte)162, (byte)93, (byte)222, (byte)239, (byte)189, (byte)72, (byte)15, (byte)79, (byte)136, (byte)210, (byte)44, (byte)233, (byte)99, (byte)72, (byte)234, (byte)225, (byte)245, (byte)27, (byte)111, (byte)175, (byte)132, (byte)112, (byte)135, (byte)253, (byte)66, (byte)215, (byte)168, (byte)156, (byte)168, (byte)79, (byte)78, (byte)140, (byte)14, (byte)129, (byte)37, (byte)238, (byte)196, (byte)34, (byte)245, (byte)141, (byte)148, (byte)161, (byte)29, (byte)110, (byte)32, (byte)255, (byte)247, (byte)52, (byte)48, (byte)102, (byte)42, (byte)54, (byte)97, (byte)185, (byte)10, (byte)114, (byte)225, (byte)247, (byte)254, (byte)108, (byte)116, (byte)73, (byte)84, (byte)242, (byte)86, (byte)15, (byte)72, (byte)68, (byte)172, (byte)74, (byte)107, (byte)103, (byte)222, (byte)246, (byte)152, (byte)67, (byte)12, (byte)104, (byte)245, (byte)20, (byte)112, (byte)94, (byte)197, (byte)201, (byte)89, (byte)182, (byte)214, (byte)6, (byte)182, (byte)165, (byte)209, (byte)79, (byte)192, (byte)211, (byte)163, (byte)208, (byte)12, (byte)73, (byte)53, (byte)99, (byte)59, (byte)182, (byte)186, (byte)48, (byte)184, (byte)215, (byte)22, (byte)24, (byte)233, (byte)109, (byte)206, (byte)59, (byte)0, (byte)118, (byte)141, (byte)25, (byte)50, (byte)242, (byte)247, (byte)240, (byte)238, (byte)127, (byte)236, (byte)241, (byte)224, (byte)20, (byte)61, (byte)65, (byte)148, (byte)120, (byte)192, (byte)99, (byte)172, (byte)194, (byte)135, (byte)61, (byte)147, (byte)251, (byte)161, (byte)219, (byte)252, (byte)187, (byte)154, (byte)115, (byte)193, (byte)118, (byte)167, (byte)130, (byte)174, (byte)211, (byte)236, (byte)141, (byte)14, (byte)8, (byte)244, (byte)110, (byte)66, (byte)210, (byte)110, (byte)236, (byte)255, (byte)25, (byte)16, (byte)134, (byte)70, (byte)196, (byte)163, (byte)30, (byte)177, (byte)238, (byte)225, (byte)237, (byte)12, (byte)14, (byte)215, (byte)40, (byte)77, (byte)206, (byte)76, (byte)122, (byte)205, (byte)20, (byte)183, (byte)106, (byte)230, (byte)230, (byte)123, (byte)209, (byte)77, (byte)102, (byte)65, (byte)241, (byte)41, (byte)213, (byte)219, (byte)79, (byte)37, (byte)61, (byte)10, (byte)154, (byte)19, (byte)93, (byte)33, (byte)72, (byte)105, (byte)247, (byte)221, (byte)145, (byte)179, (byte)69, (byte)38, (byte)234, (byte)163, (byte)218, (byte)131, (byte)179, (byte)30, (byte)114, (byte)150, (byte)106, (byte)17, (byte)187, (byte)229, (byte)106, (byte)7, (byte)112 + private static final byte[] FILE_CONTENTS = new byte[]{ + (byte)47, (byte)85, (byte)135, (byte)12, (byte)53, (byte)7, (byte)158, (byte)212, (byte)55, (byte)193, (byte)149, (byte)3, (byte)166, (byte)181, + (byte)151, (byte)84, (byte)122, (byte)200, (byte)244, (byte)5, (byte)115, (byte)159, (byte)66, (byte)64, (byte)143, (byte)211, (byte)13, (byte)63, + (byte)235, (byte)184, (byte)51, (byte)49, (byte)143, (byte)167, (byte)231, (byte)31, (byte)78, (byte)234, (byte)145, (byte)105, (byte)190, (byte)170, + (byte)49, (byte)135, (byte)175, (byte)106, (byte)25, (byte)86, (byte)145, (byte)181, (byte)156, (byte)23, (byte)153, (byte)99, (byte)175, (byte)63, + (byte)43, (byte)208, (byte)5, (byte)16, (byte)140, (byte)103, (byte)146, (byte)254, (byte)155, (byte)97, (byte)53, (byte)100, (byte)137, (byte)6, + (byte)62, (byte)101, (byte)189, (byte)137, (byte)140, (byte)5, (byte)110, (byte)218, (byte)113, (byte)132, (byte)36, (byte)188, (byte)19, (byte)168, + (byte)93, (byte)169, (byte)124, (byte)253, (byte)201, (byte)233, (byte)21, (byte)80, (byte)4, (byte)56, (byte)0, (byte)204, (byte)205, (byte)232, + (byte)213, (byte)253, (byte)232, (byte)91, (byte)153, (byte)169, (byte)82, (byte)247, (byte)78, (byte)71, (byte)188, (byte)71, (byte)23, (byte)171, + (byte)232, (byte)26, (byte)146, (byte)189, (byte)145, (byte)82, (byte)79, (byte)148, (byte)1, (byte)201, (byte)243, (byte)73, (byte)98, (byte)65, + (byte)236, (byte)177, (byte)211, (byte)106, (byte)105, (byte)46, (byte)204, (byte)214, (byte)55, (byte)182, (byte)55, (byte)149, (byte)221, (byte)52, + (byte)186, (byte)122, (byte)255, (byte)195, (byte)60, (byte)146, (byte)21, (byte)212, (byte)139, (byte)38, (byte)146, (byte)166, (byte)14, (byte)174, + (byte)242, (byte)145, (byte)16, (byte)44, (byte)68, (byte)89, (byte)25, (byte)219, (byte)62, (byte)227, (byte)6, (byte)89, (byte)194, (byte)146, (byte)93, + (byte)167, (byte)230, (byte)90, (byte)59, (byte)35, (byte)136, (byte)37, (byte)196, (byte)118, (byte)16, (byte)28, (byte)107, (byte)105, (byte)87, + (byte)195, (byte)86, (byte)87, (byte)180, (byte)176, (byte)118, (byte)6, (byte)29, (byte)26, (byte)51, (byte)94, (byte)21, (byte)23, (byte)32, (byte)156, + (byte)150, (byte)204, (byte)53, (byte)110, (byte)134, (byte)153, (byte)138, (byte)247, (byte)98, (byte)135, (byte)249, (byte)119, (byte)121, (byte)2, + (byte)42, (byte)62, (byte)198, (byte)197, (byte)112, (byte)153, (byte)244, (byte)174, (byte)145, (byte)54, (byte)246, (byte)44, (byte)198, (byte)50, + (byte)2, (byte)37, (byte)102, (byte)50, (byte)103, (byte)207, (byte)81, (byte)62, (byte)138, (byte)164, (byte)140, (byte)64, (byte)247, (byte)115, + (byte)40, (byte)41, (byte)252, (byte)54, (byte)189, (byte)207, (byte)124, (byte)147, (byte)122, (byte)243, (byte)83, (byte)34, (byte)160, (byte)64, (byte)189, (byte)226, (byte)202, (byte)181, (byte)55, (byte)158, (byte)121, (byte)78, (byte)143, (byte)41, (byte)58, (byte)27, (byte)77, (byte)186, (byte)214, (byte)23, (byte)132, (byte)100, (byte)180, (byte)26, (byte)37, (byte)247, (byte)254, (byte)97, (byte)214, (byte)57, (byte)30, (byte)46, (byte)96, (byte)44, (byte)138, (byte)15, (byte)162, (byte)93, (byte)222, (byte)239, (byte)189, (byte)72, (byte)15, (byte)79, (byte)136, (byte)210, (byte)44, (byte)233, (byte)99, (byte)72, (byte)234, (byte)225, (byte)245, (byte)27, (byte)111, (byte)175, (byte)132, (byte)112, (byte)135, (byte)253, (byte)66, (byte)215, (byte)168, (byte)156, (byte)168, (byte)79, (byte)78, (byte)140, (byte)14, (byte)129, (byte)37, (byte)238, (byte)196, (byte)34, (byte)245, (byte)141, (byte)148, (byte)161, (byte)29, (byte)110, (byte)32, (byte)255, (byte)247, (byte)52, (byte)48, (byte)102, (byte)42, (byte)54, (byte)97, (byte)185, (byte)10, (byte)114, (byte)225, (byte)247, (byte)254, (byte)108, (byte)116, (byte)73, (byte)84, (byte)242, (byte)86, (byte)15, (byte)72, (byte)68, (byte)172, (byte)74, (byte)107, (byte)103, (byte)222, (byte)246, (byte)152, (byte)67, (byte)12, (byte)104, (byte)245, (byte)20, (byte)112, (byte)94, (byte)197, (byte)201, (byte)89, (byte)182, (byte)214, (byte)6, (byte)182, (byte)165, (byte)209, (byte)79, (byte)192, (byte)211, (byte)163, (byte)208, (byte)12, (byte)73, (byte)53, (byte)99, (byte)59, (byte)182, (byte)186, (byte)48, (byte)184, (byte)215, (byte)22, (byte)24, (byte)233, (byte)109, (byte)206, (byte)59, (byte)0, (byte)118, (byte)141, (byte)25, (byte)50, (byte)242, (byte)247, (byte)240, (byte)238, (byte)127, (byte)236, (byte)241, (byte)224, (byte)20, (byte)61, (byte)65, (byte)148, (byte)120, (byte)192, (byte)99, (byte)172, (byte)194, (byte)135, (byte)61, (byte)147, (byte)251, (byte)161, (byte)219, (byte)252, (byte)187, (byte)154, (byte)115, (byte)193, (byte)118, (byte)167, (byte)130, (byte)174, (byte)211, (byte)236, (byte)141, (byte)14, (byte)8, (byte)244, (byte)110, (byte)66, (byte)210, (byte)110, (byte)236, (byte)255, (byte)25, (byte)16, (byte)134, (byte)70, (byte)196, (byte)163, (byte)30, (byte)177, (byte)238, (byte)225, (byte)237, (byte)12, (byte)14, (byte)215, (byte)40, (byte)77, (byte)206, (byte)76, (byte)122, (byte)205, (byte)20, (byte)183, (byte)106, (byte)230, (byte)230, (byte)123, (byte)209, (byte)77, (byte)102, (byte)65, (byte)241, (byte)41, (byte)213, (byte)219, (byte)79, (byte)37, (byte)61, (byte)10, (byte)154, (byte)19, (byte)93, (byte)33, (byte)72, (byte)105, (byte)247, (byte)221, (byte)145, (byte)179, (byte)69, (byte)38, (byte)234, (byte)163, (byte)218, (byte)131, (byte)179, (byte)30, (byte)114, (byte)150, (byte)106, (byte)17, (byte)187, (byte)229, (byte)106, (byte)7, (byte)112 }; private static ObjectMapper objectMapper = new ObjectMapper(); private static ResourceConfig app = new ResourceConfig().packages("com.amazonaws.serverless.proxy.jersey") - .register(MultiPartFeature.class) - .register(new ResourceBinder()) - .property("jersey.config.server.tracing.type", "ALL") - .property("jersey.config.server.tracing.threshold", "VERBOSE"); + .register(MultiPartFeature.class) + .register(new ResourceBinder()) + .property("jersey.config.server.tracing.type", "ALL") + .property("jersey.config.server.tracing.threshold", "VERBOSE"); 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"); + .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 String type; - public JerseyParamEncodingTest(String reqType) { + public void initJerseyParamEncodingTest(String reqType) { type = reqType; LambdaContainerHandler.getContainerConfig().addBinaryContentTypes(MediaType.MULTIPART_FORM_DATA); } - @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); + return Arrays.asList(new Object[]{"API_GW", "ALB", "HTTP_API"}); } private AwsProxyRequestBuilder getRequestBuilder(String path, String method) { @@ -117,11 +114,13 @@ private AwsProxyResponse executeRequest(AwsProxyRequestBuilder requestBuilder, C } } - @Test - public void queryString_uriInfo_echo() { + @MethodSource("data") + @ParameterizedTest + void queryString_uriInfo_echo(String reqType) { + initJerseyParamEncodingTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/query-string", "GET") - .json() - .queryString(QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); + .json() + .queryString(QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -130,11 +129,13 @@ public void queryString_uriInfo_echo() { validateMapResponseModel(output, QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); } - @Test - public void queryString_notEncoded_echo() { + @MethodSource("data") + @ParameterizedTest + void queryString_notEncoded_echo(String reqType) { + initJerseyParamEncodingTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/query-string", "GET") - .json() - .queryString(QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); + .json() + .queryString(QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -143,12 +144,14 @@ public void queryString_notEncoded_echo() { validateMapResponseModel(output, QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); } - @Test - @Ignore("We expect to only receive decoded values from API Gateway") - public void queryString_encoded_echo() { + @ParameterizedTest + @Disabled("We expect to only receive decoded values from API Gateway") + @MethodSource("data") + void queryString_encoded_echo(String reqType) { + initJerseyParamEncodingTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/query-string", "GET") - .json() - .queryString(QUERY_STRING_KEY, QUERY_STRING_ENCODED_VALUE); + .json() + .queryString(QUERY_STRING_KEY, QUERY_STRING_ENCODED_VALUE); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -157,8 +160,10 @@ public void queryString_encoded_echo() { validateMapResponseModel(output, QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); } - @Test - public void simpleQueryParam_encoding_expectDecodedParam() { + @MethodSource("data") + @ParameterizedTest + void simpleQueryParam_encoding_expectDecodedParam(String reqType) { + initJerseyParamEncodingTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/decoded-param", "GET").queryString("param", SIMPLE_ENCODED_PARAM); AwsProxyResponse resp = executeRequest(request, lambdaContext); @@ -166,8 +171,10 @@ public void simpleQueryParam_encoding_expectDecodedParam() { validateSingleValueModel(resp, SIMPLE_ENCODED_PARAM); } - @Test - public void jsonQueryParam_encoding_expectDecodedParam() { + @MethodSource("data") + @ParameterizedTest + void jsonQueryParam_encoding_expectDecodedParam(String reqType) { + initJerseyParamEncodingTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/decoded-param", "GET").queryString("param", JSON_ENCODED_PARAM); AwsProxyResponse resp = executeRequest(request, lambdaContext); @@ -175,8 +182,10 @@ public void jsonQueryParam_encoding_expectDecodedParam() { validateSingleValueModel(resp, JSON_ENCODED_PARAM); } - @Test - public void simpleQueryParam_encoding_expectEncodedParam() { + @MethodSource("data") + @ParameterizedTest + void simpleQueryParam_encoding_expectEncodedParam(String reqType) { + initJerseyParamEncodingTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", SIMPLE_ENCODED_PARAM); String encodedVal = ""; try { @@ -189,8 +198,10 @@ public void simpleQueryParam_encoding_expectEncodedParam() { validateSingleValueModel(resp, encodedVal); } - @Test - public void jsonQueryParam_encoding_expectEncodedParam() { + @MethodSource("data") + @ParameterizedTest + void jsonQueryParam_encoding_expectEncodedParam(String reqType) { + initJerseyParamEncodingTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", JSON_ENCODED_PARAM); String encodedVal = ""; try { @@ -203,52 +214,62 @@ public void jsonQueryParam_encoding_expectEncodedParam() { validateSingleValueModel(resp, encodedVal); } - @Test - public void queryParam_encoding_expectFullyEncodedUrl() { + @MethodSource("data") + @ParameterizedTest + void queryParam_encoding_expectFullyEncodedUrl(String reqType) { + initJerseyParamEncodingTest(reqType); String paramValue = "/+="; AwsProxyRequestBuilder request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", paramValue); AwsProxyResponse resp = executeRequest(request, lambdaContext); assertNotNull(resp); - assertEquals(resp.getStatusCode(), 200); + assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, "%2F%2B%3D"); } - @Test - public void pathParam_encoded_routesToCorrectPath() { + @MethodSource("data") + @ParameterizedTest + void pathParam_encoded_routesToCorrectPath(String reqType) { + initJerseyParamEncodingTest(reqType); String encodedParam = "http%3A%2F%2Fhelloresource.com"; String path = "/echo/encoded-path/" + encodedParam; AwsProxyRequestBuilder request = getRequestBuilder(path, "GET"); AwsProxyResponse resp = executeRequest(request, lambdaContext); assertNotNull(resp); - assertEquals(resp.getStatusCode(), 200); + assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, encodedParam); } - @Test - public void pathParam_encoded_returns404() { + @MethodSource("data") + @ParameterizedTest + void pathParam_encoded_returns404(String reqType) { + initJerseyParamEncodingTest(reqType); String encodedParam = "http://helloresource.com"; String path = "/echo/encoded-path/" + encodedParam; AwsProxyRequestBuilder request = getRequestBuilder(path, "GET"); AwsProxyResponse resp = executeRequest(request, lambdaContext); assertNotNull(resp); - assertEquals(resp.getStatusCode(), 404); + assertEquals(404, resp.getStatusCode()); } - @Test - @Ignore - public void queryParam_listOfString_expectCorrectLength() { + @ParameterizedTest + @Disabled + @MethodSource("data") + void queryParam_listOfString_expectCorrectLength(String reqType) { + initJerseyParamEncodingTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/list-query-string", "GET").queryString("list", "v1,v2,v3"); AwsProxyResponse resp = executeRequest(request, lambdaContext); assertNotNull(resp); - assertEquals(resp.getStatusCode(), 200); + assertEquals(200, resp.getStatusCode()); validateSingleValueModel(resp, "3"); } - @Test - public void multipart_getFileSize_expectCorrectLength() - throws IOException { + @MethodSource("data") + @ParameterizedTest + void multipart_getFileSize_expectCorrectLength(String reqType) + throws IOException { + initJerseyParamEncodingTest(reqType); AwsProxyRequestBuilder request = getRequestBuilder("/echo/file-size", "POST") - .formFilePart("file", "myfile.jpg", FILE_CONTENTS); + .formFilePart("file", "myfile.jpg", FILE_CONTENTS); AwsProxyResponse resp = executeRequest(request, lambdaContext); assertNotNull(resp); assertEquals(200, resp.getStatusCode()); diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/ResourceBinder.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/ResourceBinder.java index e98ea805a..f35bffd49 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/ResourceBinder.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/ResourceBinder.java @@ -2,7 +2,7 @@ import org.glassfish.jersey.internal.inject.AbstractBinder; -import javax.inject.Singleton; +import jakarta.inject.Singleton; public class ResourceBinder extends AbstractBinder { @Override diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/CustomExceptionMapper.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/CustomExceptionMapper.java index e435aff96..61cddee1b 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/CustomExceptionMapper.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/CustomExceptionMapper.java @@ -1,11 +1,11 @@ package com.amazonaws.serverless.proxy.jersey.providers; -import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; -import javax.ws.rs.ext.Provider; +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; @Provider @@ -16,7 +16,7 @@ public CustomExceptionMapper() { } @Inject - public javax.inject.Provider request; + public jakarta.inject.Provider request; @Override public Response toResponse(UnsupportedOperationException throwable) { diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/ServletRequestFilter.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/ServletRequestFilter.java index 70d65a64c..8ccfe488f 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/ServletRequestFilter.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/ServletRequestFilter.java @@ -1,10 +1,10 @@ package com.amazonaws.serverless.proxy.jersey.providers; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.core.Context; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.core.Context; import java.io.IOException; diff --git a/aws-serverless-java-container-spark/pom.xml b/aws-serverless-java-container-spark/pom.xml deleted file mode 100644 index 9da5aa343..000000000 --- a/aws-serverless-java-container-spark/pom.xml +++ /dev/null @@ -1,131 +0,0 @@ - - - 4.0.0 - - aws-serverless-java-container-spark - AWS Serverless Java container support - Spark implementation - Allows Java applications written for Spark to run in AWS Lambda - https://aws.amazon.com/lambda - 1.10-SNAPSHOT - - - com.amazonaws.serverless - aws-serverless-java-container - 1.10-SNAPSHOT - .. - - - - 2.9.4 - - - - - - com.amazonaws.serverless - aws-serverless-java-container-core - 1.10-SNAPSHOT - - - - - com.sparkjava - spark-core - ${spark.version} - - - - - - - org.jacoco - jacoco-maven-plugin - - ${basedir}/target/coverage-reports/jacoco-unit.exec - ${basedir}/target/coverage-reports/jacoco-unit.exec - - - - default-prepare-agent - - prepare-agent - - - - jacoco-site - package - - report - - - - jacoco-check - test - - check - - - true - - BUNDLE - - - INSTRUCTION - COVEREDRATIO - ${jacoco.minCoverage} - - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.9 - - always - - - - com.github.spotbugs - spotbugs-maven-plugin - - - - analyze-compile - compile - - check - - - - - - org.owasp - dependency-check-maven - ${dependencyCheck.version} - - true - - ${project.basedir}/../owasp-suppression.xml - - 7 - false - - - - - check - - - - - - - - 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 deleted file mode 100644 index 6b2168aa9..000000000 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -package com.amazonaws.serverless.proxy.spark; - - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -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; - -import com.amazonaws.services.lambda.runtime.Context; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spark.Service; -import spark.Spark; -import spark.embeddedserver.EmbeddedServers; - -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; -import java.lang.reflect.Method; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.EnumSet; -import java.util.concurrent.CountDownLatch; - - -/** - * Implementation of the LambdaContainerHandler object that supports the Spark framework: http://sparkjava.com/ - *

- * Because of the way this container is implemented, using reflection to change accessibility of methods in the Spark - * framework and inserting itself as the default embedded container, it is important that you initialize the Handler - * before declaring your spark routes. - *

- * This implementation uses the default AwsProxyHttpServletRequest and Response implementations. - *

- *

- * {@code
- *     // always initialize the handler first
- *     SparkLambdaContainerHandler handler =
- *             SparkLambdaContainerHandler.getAwsProxyHandler();
- *
- *     get("/hello", (req, res) -> {
- *         res.status(200);
- *         res.body("Hello World");
- *     });
- * }
- * 
- * - * @param The request object used by the RequestReader implementation passed to the constructor - * @param The response object produced by the ResponseWriter implementation in the constructor - */ -public class SparkLambdaContainerHandler - extends AwsLambdaServletContainerHandler { - - //------------------------------------------------------------- - // Constants - //------------------------------------------------------------- - - private static final String LAMBDA_EMBEDDED_SERVER_CODE = "AWS_LAMBDA"; - - //------------------------------------------------------------- - // Variables - Private - //------------------------------------------------------------- - - private LambdaEmbeddedServer embeddedServer; - private LambdaEmbeddedServerFactory lambdaServerFactory; - private Logger log = LoggerFactory.getLogger(SparkLambdaContainerHandler.class); - - //------------------------------------------------------------- - // Methods - Public - Static - //------------------------------------------------------------- - - - /** - * Returns a new instance of an SparkLambdaContainerHandler initialized to work with AwsProxyRequest - * 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 getAwsProxyHandler() - throws ContainerInitializationException { - SparkLambdaContainerHandler newHandler = new SparkLambdaContainerHandler<>(AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - 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; - } - - /** - * 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(true), - 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 - //------------------------------------------------------------- - - - public SparkLambdaContainerHandler(Class requestTypeClass, - Class responseTypeClass, - RequestReader requestReader, - ResponseWriter responseWriter, - SecurityContextWriter securityContextWriter, - ExceptionHandler exceptionHandler, - LambdaEmbeddedServerFactory embeddedServerFactory) - throws ContainerInitializationException { - super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); - Timer.start("SPARK_CONTAINER_HANDLER_CONSTRUCTOR"); - - EmbeddedServers.add(LAMBDA_EMBEDDED_SERVER_CODE, embeddedServerFactory); - this.lambdaServerFactory = embeddedServerFactory; - - // TODO: This is pretty bad but we are not given access to the embeddedServerIdentifier property of the - // Service object - try { - AccessController.doPrivileged((PrivilegedExceptionAction) () -> { - log.debug("Changing visibility of getInstance method and embeddedServerIdentifier properties"); - Method serviceInstanceMethod = Spark.class.getDeclaredMethod("getInstance"); - serviceInstanceMethod.setAccessible(true); - Service sparkService = (Service) serviceInstanceMethod.invoke(null); - Field serverIdentifierField = Service.class.getDeclaredField("embeddedServerIdentifier"); - serverIdentifierField.setAccessible(true); - serverIdentifierField.set(sparkService, LAMBDA_EMBEDDED_SERVER_CODE); - return null; - }); - } catch (PrivilegedActionException e) { - if (e.getException() instanceof NoSuchFieldException) { - log.error("Could not fine embeddedServerIdentifier field in Service class", e.getException()); - } else if (e.getException() instanceof NoSuchMethodException) { - log.error("Could not find getInstance method in Spark class", e.getException()); - } else if (e.getException() instanceof IllegalAccessException) { - log.error("Could not access getInstance method in Spark class", e.getException()); - } else if (e.getException() instanceof InvocationTargetException) { - log.error("Could not invoke getInstance method in Spark class", e.getException()); - } else { - log.error("Unknown exception while modifying Spark class", e.getException()); - } - Timer.stop("SPARK_CONTAINER_HANDLER_CONSTRUCTOR"); - throw new ContainerInitializationException("Could not initialize Spark server", e.getException()); - } - Timer.stop("SPARK_CONTAINER_HANDLER_CONSTRUCTOR"); - } - - //------------------------------------------------------------- - // Methods - Implementation - //------------------------------------------------------------- - - - @Override - protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { - return new AwsHttpServletResponse(request, latch); - } - - - @Override - protected void handleRequest(HttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext) - throws Exception { - Timer.start("SPARK_HANDLE_REQUEST"); - - if (embeddedServer == null) { - initialize(); - } - - if (AwsHttpServletRequest.class.isAssignableFrom(httpServletRequest.getClass())) { - ((AwsHttpServletRequest)httpServletRequest).setServletContext(getServletContext()); - } - - doFilter(httpServletRequest, httpServletResponse, null); - Timer.stop("SPARK_HANDLE_REQUEST"); - } - - - @Override - public void initialize() - throws ContainerInitializationException { - Timer.start("SPARK_COLD_START"); - log.debug("First request, getting new server instance"); - - // trying to call init in case the embedded server had not been initialized. - Spark.init(); - - // adding this call to make sure that the framework is fully initialized. This should address a race - // condition and solve GitHub issue #71. - Spark.awaitInitialization(); - - embeddedServer = lambdaServerFactory.getServerInstance(); - - // manually add the spark filter to the chain. This should the last one and match all uris - FilterRegistration.Dynamic sparkRegistration = getServletContext().addFilter("SparkFilter", embeddedServer.getSparkFilter()); - sparkRegistration.addMappingForUrlPatterns( - EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.INCLUDE, DispatcherType.FORWARD), - true, "/*"); - Timer.stop("SPARK_COLD_START"); - } - - public Servlet getServlet() { - return null; - } -} 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 deleted file mode 100644 index fadca4941..000000000 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServer.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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; -import com.amazonaws.serverless.proxy.internal.testutils.Timer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spark.ExceptionMapper; -import spark.embeddedserver.EmbeddedServer; -import spark.embeddedserver.jetty.websocket.WebSocketHandlerWrapper; -import spark.http.matching.MatcherFilter; -import spark.route.Routes; -import spark.ssl.SslStores; -import spark.staticfiles.StaticFilesConfiguration; - -import javax.servlet.Filter; -import java.util.Map; -import java.util.Optional; - -public class LambdaEmbeddedServer - implements EmbeddedServer { - - //------------------------------------------------------------- - // Variables - Private - //------------------------------------------------------------- - - private Routes applicationRoutes; - private ExceptionMapper exceptionMapper; - private MatcherFilter sparkFilter; - private StaticFilesConfiguration staticFilesConfiguration; - private boolean hasMultipleHandler; - private Logger log = LoggerFactory.getLogger(LambdaEmbeddedServer.class); - - - //------------------------------------------------------------- - // Constructors - //------------------------------------------------------------- - - LambdaEmbeddedServer(Routes routes, StaticFilesConfiguration filesConfig, ExceptionMapper exceptionMapper, boolean multipleHandlers) { - Timer.start("SPARK_EMBEDDED_SERVER_CONSTRUCTOR"); - applicationRoutes = routes; - staticFilesConfiguration = filesConfig; - hasMultipleHandler = multipleHandlers; - this.exceptionMapper = exceptionMapper; - - // try to initialize the filter here. - sparkFilter = new MatcherFilter(applicationRoutes, staticFilesConfiguration, exceptionMapper, true, hasMultipleHandler); - Timer.stop("SPARK_EMBEDDED_SERVER_CONSTRUCTOR"); - } - - - //------------------------------------------------------------- - // Implementation - EmbeddedServer - //------------------------------------------------------------- - @Override - public int ignite(String host, int port, SslStores sslStores, int maxThreads, int minThreads, int threadIdleTimeoutMillis) - throws ContainerInitializationException { - Timer.start("SPARK_EMBEDDED_IGNITE"); - log.info("Starting Spark server, ignoring port and host"); - // if not initialized yet - if (sparkFilter == null) { - sparkFilter = new MatcherFilter(applicationRoutes, staticFilesConfiguration, exceptionMapper, true, hasMultipleHandler); - } - sparkFilter.init(null); - Timer.stop("SPARK_EMBEDDED_IGNITE"); - return port; - } - - - public void configureWebSockets(Map webSocketHandlers, - Optional webSocketIdleTimeoutMillis) { - // Swallowing this exception to prevent Spark from getting stuck - // throw new UnsupportedOperationException(); - log.info("Spark called configureWebSockets. However, web sockets are not supported"); - } - - - @Override - public void join() { - log.info("Called join method, nothing to do here since Lambda only runs a single event per container"); - } - - - @Override - public void extinguish() { - log.info("Called extinguish method, nothing to do here."); - } - - - @Override - public int activeThreadCount() { - log.debug("Called activeThreadCount, since Lambda only runs one event per container we always return 1"); - return 1; - } - - //------------------------------------------------------------- - // Methods - Public - //------------------------------------------------------------- - - /** - * Returns the initialized instance of the main Spark filter. - * @return The spark filter instance. - */ - public Filter getSparkFilter() { - return sparkFilter; - } -} 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 deleted file mode 100644 index 9074e5ddf..000000000 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerFactory.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import spark.ExceptionMapper; -import spark.embeddedserver.EmbeddedServer; -import spark.embeddedserver.EmbeddedServerFactory; -import spark.route.Routes; -import spark.staticfiles.StaticFilesConfiguration; - -public class LambdaEmbeddedServerFactory implements EmbeddedServerFactory { - - //------------------------------------------------------------- - // Variables - Private - Static - //------------------------------------------------------------- - - private static volatile LambdaEmbeddedServer embeddedServer; - - - /** - * Empty constructor, applications should always use this constructor. - */ - public LambdaEmbeddedServerFactory() {} - - - /** - * Constructor used in unit tests to inject a mocked embedded server - * @param server The mocked server - */ - @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD") // suppressing the warning as this constructor is only used for unit tests - public LambdaEmbeddedServerFactory(LambdaEmbeddedServer server) { - LambdaEmbeddedServerFactory.embeddedServer = server; - } - - - //------------------------------------------------------------- - // Implementation - EmbeddedServerFactory - //------------------------------------------------------------- - - - @Override - public EmbeddedServer create(Routes routes, StaticFilesConfiguration staticFilesConfiguration, ExceptionMapper exceptionMapper, boolean multipleHandlers) { - Timer.start("SPARK_SERVER_FACTORY_CREATE"); - if (embeddedServer == null) { - LambdaEmbeddedServerFactory.embeddedServer = new LambdaEmbeddedServer(routes, staticFilesConfiguration, exceptionMapper, multipleHandlers); - } - Timer.stop("SPARK_SERVER_FACTORY_CREATE"); - return embeddedServer; - } - - - //------------------------------------------------------------- - // Methods - Getter/Setter - //------------------------------------------------------------- - - public LambdaEmbeddedServer getServerInstance() { - return embeddedServer; - } -} 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 deleted file mode 100644 index 0c41f91f7..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkStreamTest.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.amazonaws.serverless.proxy.spark; - - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -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.model.HttpApiV2ProxyRequest; -import com.amazonaws.services.lambda.runtime.Context; -import org.junit.AfterClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import spark.Spark; - -import javax.servlet.http.Cookie; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static spark.Spark.get; - -// This class doesn't actually test Spark. Instead it tests the proxyStream method of the -// LambdaContainerHandler object. We use the Spark implementation for this because it's the -// fastest to start -@RunWith(Parameterized.class) -public class HelloWorldSparkStreamTest { - private static final String CUSTOM_HEADER_KEY = "X-Custom-Header"; - private static final String CUSTOM_HEADER_VALUE = "My Header Value"; - private static final String BODY_TEXT_RESPONSE = "Hello World"; - - private static final String COOKIE_NAME = "MyCookie"; - private static final String COOKIE_VALUE = "CookieValue"; - private static final String COOKIE_DOMAIN = "mydomain.com"; - private static final String COOKIE_PATH = "/"; - - private static SparkLambdaContainerHandler handler; - private static SparkLambdaContainerHandler httpApiHandler; - - private String type; - - 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[] { "API_GW", "ALB", "HTTP_API" }); - } - - private AwsProxyRequestBuilder getRequestBuilder() { - return new AwsProxyRequestBuilder(); - } - - 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 - public static void stopSpark() { - Spark.stop(); - } - - @Test - public void helloRequest_basicStream_populatesOutputSuccessfully() { - try { - ByteArrayOutputStream outputStream = executeRequest(getRequestBuilder().method("GET").path("/hello"), new MockLambdaContext()); - AwsProxyResponse response = LambdaContainerHandler.getObjectMapper().readValue(outputStream.toByteArray(), AwsProxyResponse.class); - - assertEquals(200, response.getStatusCode()); - assertTrue(response.getMultiValueHeaders().containsKey(CUSTOM_HEADER_KEY)); - assertEquals(CUSTOM_HEADER_VALUE, response.getMultiValueHeaders().getFirst(CUSTOM_HEADER_KEY)); - assertEquals(BODY_TEXT_RESPONSE, response.getBody()); - } catch (IOException e) { - e.printStackTrace(); - fail(); - } - } - - @Test - public void nullPathRequest_doesntFail_expectA404() { - try { - ByteArrayOutputStream outputStream = executeRequest(getRequestBuilder().method("GET").path(null), new MockLambdaContext()); - AwsProxyResponse response = LambdaContainerHandler.getObjectMapper().readValue(outputStream.toByteArray(), AwsProxyResponse.class); - - assertEquals(404, response.getStatusCode()); - } catch (IOException e) { - e.printStackTrace(); - fail(); - } - } - - private static void configureRoutes() { - get("/hello", (req, res) -> { - res.status(200); - res.header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); - return BODY_TEXT_RESPONSE; - }); - - get("/cookie", (req, res) -> { - Cookie testCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); - testCookie.setDomain(COOKIE_DOMAIN); - testCookie.setPath(COOKIE_PATH); - res.raw().addCookie(testCookie); - return BODY_TEXT_RESPONSE; - }); - - get("/multi-cookie", (req, res) -> { - Cookie testCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); - testCookie.setDomain(COOKIE_DOMAIN); - testCookie.setPath(COOKIE_PATH); - Cookie testCookie2 = new Cookie(COOKIE_NAME + "2", COOKIE_VALUE + "2"); - testCookie2.setDomain(COOKIE_DOMAIN); - testCookie2.setPath(COOKIE_PATH); - res.raw().addCookie(testCookie); - res.raw().addCookie(testCookie2); - return BODY_TEXT_RESPONSE; - }); - } -} diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java deleted file mode 100644 index 108eeabeb..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.amazonaws.serverless.proxy.spark; - - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; -import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; - -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import spark.Spark; - -import javax.servlet.http.Cookie; -import javax.ws.rs.core.HttpHeaders; - -import java.util.Arrays; -import java.util.Collection; - -import static org.junit.Assert.*; -import static spark.Spark.get; - - -@RunWith(Parameterized.class) -public class HelloWorldSparkTest { - private static final String CUSTOM_HEADER_KEY = "X-Custom-Header"; - private static final String CUSTOM_HEADER_VALUE = "My Header Value"; - private static final String BODY_TEXT_RESPONSE = "Hello World"; - - private static final String COOKIE_NAME = "MyCookie"; - private static final String COOKIE_VALUE = "CookieValue"; - private static final String COOKIE_DOMAIN = "mydomain.com"; - private static final String COOKIE_PATH = "/"; - - private static final String READ_COOKIE_NAME = "customCookie"; - - private static SparkLambdaContainerHandler handler; - - private boolean isAlb; - - public HelloWorldSparkTest(boolean alb) { - isAlb = alb; - } - - @Parameterized.Parameters - public static Collection data() { - return Arrays.asList(new Object[] { false, true }); - } - - private AwsProxyRequestBuilder getRequestBuilder() { - AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder(); - if (isAlb) builder.alb(); - - return builder; - } - - @BeforeClass - public static void initializeServer() { - try { - handler = SparkLambdaContainerHandler.getAwsProxyHandler(); - - configureRoutes(); - Spark.awaitInitialization(); - } catch (RuntimeException | ContainerInitializationException e) { - e.printStackTrace(); - fail(); - } - } - - @AfterClass - public static void stopSpark() { - Spark.stop(); - } - - @Test - public void basicServer_handleRequest_emptyFilters() { - AwsProxyRequest req = getRequestBuilder().method("GET").path("/hello").build(); - AwsProxyResponse response = handler.proxy(req, new MockLambdaContext()); - - assertEquals(200, response.getStatusCode()); - assertTrue(response.getMultiValueHeaders().containsKey(CUSTOM_HEADER_KEY)); - assertEquals(CUSTOM_HEADER_VALUE, response.getMultiValueHeaders().getFirst(CUSTOM_HEADER_KEY)); - assertEquals(BODY_TEXT_RESPONSE, response.getBody()); - } - - @Test - public void addCookie_setCookieOnResponse_validCustomCookie() { - AwsProxyRequest req = getRequestBuilder().method("GET").path("/cookie").build(); - AwsProxyResponse response = handler.proxy(req, new MockLambdaContext()); - - assertEquals(200, response.getStatusCode()); - assertTrue(response.getMultiValueHeaders().containsKey(HttpHeaders.SET_COOKIE)); - assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.SET_COOKIE).contains(COOKIE_NAME + "=" + COOKIE_VALUE)); - assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.SET_COOKIE).contains(COOKIE_DOMAIN)); - assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.SET_COOKIE).contains(COOKIE_PATH)); - } - - @Test - public void multiCookie_setCookieOnResponse_singleHeaderWithMultipleValues() { - AwsProxyRequest req = getRequestBuilder().method("GET").path("/multi-cookie").build(); - AwsProxyResponse response = handler.proxy(req, new MockLambdaContext()); - - assertEquals(200, response.getStatusCode()); - assertTrue(response.getMultiValueHeaders().containsKey(HttpHeaders.SET_COOKIE)); - - assertEquals(2, response.getMultiValueHeaders().get(HttpHeaders.SET_COOKIE).size()); - assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.SET_COOKIE).contains(COOKIE_NAME + "=" + COOKIE_VALUE)); - assertTrue(response.getMultiValueHeaders().get(HttpHeaders.SET_COOKIE).get(1).contains(COOKIE_NAME + "2=" + COOKIE_VALUE + "2")); - assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.SET_COOKIE).contains(COOKIE_DOMAIN)); - assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.SET_COOKIE).contains(COOKIE_PATH)); - } - - @Test - public void rootResource_basicRequest_expectSuccess() { - AwsProxyRequest req = getRequestBuilder().method("GET").path("/").build(); - AwsProxyResponse response = handler.proxy(req, new MockLambdaContext()); - - assertEquals(200, response.getStatusCode()); - assertTrue(response.getMultiValueHeaders().containsKey(CUSTOM_HEADER_KEY)); - assertEquals(CUSTOM_HEADER_VALUE, response.getMultiValueHeaders().getFirst(CUSTOM_HEADER_KEY)); - assertEquals(BODY_TEXT_RESPONSE, response.getBody()); - } - - @Test - public void readCookie_customDomainName_expectValidCookie() { - AwsProxyRequest req = getRequestBuilder().method("GET").path("/cookie-read").cookie(READ_COOKIE_NAME, "test").build(); - AwsProxyResponse response = handler.proxy(req, new MockLambdaContext()); - assertEquals("test", response.getBody()); - } - - private static void configureRoutes() { - get("/", (req, res) -> { - res.status(200); - res.header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); - return BODY_TEXT_RESPONSE; - }); - - get("/hello", (req, res) -> { - res.status(200); - res.header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); - return BODY_TEXT_RESPONSE; - }); - - get("/cookie", (req, res) -> { - Cookie testCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); - testCookie.setDomain(COOKIE_DOMAIN); - testCookie.setPath(COOKIE_PATH); - res.raw().addCookie(testCookie); - return BODY_TEXT_RESPONSE; - }); - - get("/multi-cookie", (req, res) -> { - Cookie testCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE); - testCookie.setDomain(COOKIE_DOMAIN); - testCookie.setPath(COOKIE_PATH); - Cookie testCookie2 = new Cookie(COOKIE_NAME + "2", COOKIE_VALUE + "2"); - testCookie2.setDomain(COOKIE_DOMAIN); - testCookie2.setPath(COOKIE_PATH); - res.raw().addCookie(testCookie); - res.raw().addCookie(testCookie2); - return BODY_TEXT_RESPONSE; - }); - - get("/cookie-read", (req, res) -> req.cookie(READ_COOKIE_NAME)); - } -} diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java deleted file mode 100644 index 6042899e4..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.amazonaws.serverless.proxy.spark; - - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; -import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServer; -import com.amazonaws.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServerFactory; - -import org.junit.AfterClass; -import org.junit.Test; -import spark.Spark; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.when; -import static spark.Spark.get; -import static spark.Spark.initExceptionHandler; - - -public class InitExceptionHandlerTest { - - private static final String TEST_EXCEPTION_MESSAGE = "test exception"; - private static LambdaEmbeddedServer embeddedServer = mock(LambdaEmbeddedServer.class); - - @Test - public void initException_mockException_expectHandlerToRun() { - try { - - when(embeddedServer.ignite(anyString(), anyInt(), anyObject(), anyInt(), anyInt(), anyInt())) - .thenThrow(new ContainerInitializationException(TEST_EXCEPTION_MESSAGE, null)); - LambdaEmbeddedServerFactory serverFactory = new LambdaEmbeddedServerFactory(embeddedServer); - new SparkLambdaContainerHandler<>(AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler(), - serverFactory); - - configureRoutes(); - Spark.awaitInitialization(); - } catch (Exception e) { - e.printStackTrace(); - fail("Error while mocking server"); - } - - } - - @AfterClass - public static void stopSpark() { - // un-mock the embedded server to avoid blocking other tests - reset(embeddedServer); - // reset the static variable in the factory so that it will be instantiated again next time - new LambdaEmbeddedServerFactory(null); - Spark.stop(); - } - - private static void configureRoutes() { - initExceptionHandler((e) -> { - assertEquals(TEST_EXCEPTION_MESSAGE, e.getLocalizedMessage()); - }); - - get("/test-route", (req, res) -> { - res.status(200); - return "test"; - }); - } -} diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java deleted file mode 100644 index c778bd57e..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.amazonaws.serverless.proxy.spark; - - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; -import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; -import com.amazonaws.serverless.proxy.spark.filter.CustomHeaderFilter; -import com.amazonaws.serverless.proxy.spark.filter.UnauthenticatedFilter; - -import org.junit.AfterClass; -import org.junit.Test; -import spark.Spark; - -import javax.servlet.DispatcherType; -import javax.servlet.FilterRegistration; - -import java.util.EnumSet; - -import static org.junit.Assert.*; -import static spark.Spark.get; - - -public class SparkLambdaContainerHandlerTest { - private static final String RESPONSE_BODY_TEXT = "hello"; - - @Test - public void filters_onStartupMethod_executeFilters() { - - SparkLambdaContainerHandler handler = null; - try { - handler = SparkLambdaContainerHandler.getAwsProxyHandler(); - } catch (ContainerInitializationException e) { - e.printStackTrace(); - fail(); - } - - handler.onStartup(c -> { - if (c == null) { - fail(); - } - FilterRegistration.Dynamic registration = c.addFilter("CustomHeaderFilter", CustomHeaderFilter.class); - // update the registration to map to a path - registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); - // servlet name mappings are disabled and will throw an exception - }); - - configureRoutes(); - - Spark.awaitInitialization(); - - AwsProxyRequest req = new AwsProxyRequestBuilder().method("GET").path("/header-filter").build(); - AwsProxyResponse response = handler.proxy(req, new MockLambdaContext()); - - assertNotNull(response); - assertEquals(200, response.getStatusCode()); - assertTrue(response.getMultiValueHeaders().containsKey(CustomHeaderFilter.HEADER_NAME)); - assertEquals(CustomHeaderFilter.HEADER_VALUE, response.getMultiValueHeaders().getFirst(CustomHeaderFilter.HEADER_NAME)); - assertEquals(RESPONSE_BODY_TEXT, response.getBody()); - - } - - @Test - public void filters_unauthenticatedFilter_stopRequestProcessing() { - - SparkLambdaContainerHandler handler = null; - try { - handler = SparkLambdaContainerHandler.getAwsProxyHandler(); - } catch (ContainerInitializationException e) { - e.printStackTrace(); - fail(); - } - - handler.onStartup(c -> { - if (c == null) { - fail(); - } - FilterRegistration.Dynamic registration = c.addFilter("UnauthenticatedFilter", UnauthenticatedFilter.class); - // update the registration to map to a path - registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/unauth"); - // servlet name mappings are disabled and will throw an exception - }); - - configureRoutes(); - Spark.awaitInitialization(); - - // first we test without the custom header, we expect request processing to complete - // successfully - AwsProxyRequest req = new AwsProxyRequestBuilder().method("GET").path("/unauth").build(); - AwsProxyResponse response = handler.proxy(req, new MockLambdaContext()); - - assertNotNull(response); - assertEquals(200, response.getStatusCode()); - assertEquals(RESPONSE_BODY_TEXT, response.getBody()); - - // now we test with the custom header, this should stop request processing in the - // filter and return an unauthenticated response - AwsProxyRequest unauthReq = new AwsProxyRequestBuilder().method("GET").path("/unauth") - .header(UnauthenticatedFilter.HEADER_NAME, "1").build(); - AwsProxyResponse unauthResp = handler.proxy(unauthReq, new MockLambdaContext()); - - assertNotNull(unauthResp); - assertEquals(UnauthenticatedFilter.RESPONSE_STATUS, unauthResp.getStatusCode()); - assertEquals("", unauthResp.getBody()); - } - - @AfterClass - public static void stopSpark() { - Spark.stop(); - } - - private static void configureRoutes() { - get("/header-filter", (req, res) -> { - res.status(200); - return RESPONSE_BODY_TEXT; - }); - - get("/unauth", (req, res) -> { - res.status(200); - return RESPONSE_BODY_TEXT; - }); - } -} diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerTest.java deleted file mode 100644 index 968b2b47b..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.amazonaws.serverless.proxy.spark.embeddedserver; - - -import org.junit.Test; - -import java.util.Optional; - -import static org.junit.Assert.*; - - -public class LambdaEmbeddedServerTest { - private static LambdaEmbeddedServer server = new LambdaEmbeddedServer(null, null, null, false); - - @Test - public void webSocket_configureWebSocket_noException() { - try { - server.configureWebSockets(null, Optional.of(0L)); - } catch (Exception e) { - e.printStackTrace(); - fail(); - } - } -} diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/CustomHeaderFilter.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/CustomHeaderFilter.java deleted file mode 100644 index e3733f3a9..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/CustomHeaderFilter.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.amazonaws.serverless.proxy.spark.filter; - - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; - - -public class CustomHeaderFilter implements Filter { - public static final String HEADER_NAME = "X-Filter-Header"; - public static final String HEADER_VALUE = "CustomHeaderFilter"; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - HttpServletResponse resp = (HttpServletResponse)servletResponse; - resp.addHeader(HEADER_NAME, HEADER_VALUE); - - filterChain.doFilter(servletRequest, servletResponse); - } - - - @Override - public void destroy() { - - } -} \ No newline at end of file diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java deleted file mode 100644 index 742072ca5..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.amazonaws.serverless.proxy.spark.filter; - - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; - - -public class UnauthenticatedFilter implements Filter { - public static final String HEADER_NAME = "X-Unauthenticated-Response"; - public static final int RESPONSE_STATUS = 401; - - @Override - public void init(FilterConfig filterConfig) - throws ServletException { - - } - - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) - throws IOException, ServletException { - if (((HttpServletRequest)servletRequest).getHeader(HEADER_NAME) != null) { - ((HttpServletResponse) servletResponse).setStatus(401); - return; - } - filterChain.doFilter(servletRequest, servletResponse); - } - - - @Override - public void destroy() { - - } -} diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index b238a0d55..f03d9db0a 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -6,18 +6,18 @@ AWS Serverless Java container support - Spring implementation Allows Java applications written for the Spring framework to run in AWS Lambda https://aws.amazon.com/lambda - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT com.amazonaws.serverless aws-serverless-java-container - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT .. - 5.3.23 - 5.7.4 + 6.2.8 + 6.5.1 @@ -25,10 +25,17 @@ com.amazonaws.serverless aws-serverless-java-container-core - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT + + + com.amazonaws.serverless + aws-serverless-java-container-core + 2.1.5-SNAPSHOT + tests + test-jar + test - org.springframework spring-webmvc @@ -36,7 +43,6 @@ true - org.springframework spring-test @@ -44,11 +50,10 @@ test - commons-codec commons-codec - 1.15 + 1.18.0 test @@ -59,11 +64,10 @@ test - - javax.activation - activation - 1.1.1 + jakarta.activation + jakarta.activation-api + 2.1.3 test @@ -71,23 +75,22 @@ org.hibernate.validator hibernate-validator - 6.2.1.Final + 8.0.2.Final test - - javax.el - javax.el-api - 3.0.0 + org.junit.jupiter + junit-jupiter test - org.glassfish - javax.el - 3.0.0 + org.glassfish.expressly + expressly + 5.0.0 test + org.springframework.security spring-security-config @@ -199,9 +202,8 @@ org.apache.maven.plugins maven-surefire-plugin - 2.9 - always + false @@ -232,13 +234,6 @@ 7 false - - - - check - - - diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyExceptionHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyExceptionHandler.java new file mode 100644 index 000000000..21ec94166 --- /dev/null +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyExceptionHandler.java @@ -0,0 +1,24 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; +import com.amazonaws.serverless.proxy.ExceptionHandler; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import org.springframework.web.ErrorResponse; + +/** + * This ExceptionHandler implementation enhances the standard AwsProxyExceptionHandler + * by mapping additional details from org.springframework.web.ErrorResponse + */ +public class SpringAwsProxyExceptionHandler extends AwsProxyExceptionHandler + implements ExceptionHandler { + @Override + public AwsProxyResponse handle(Throwable ex) { + if (ex instanceof ErrorResponse) { + return new AwsProxyResponse(((ErrorResponse) ex).getStatusCode().value(), + HEADERS, getErrorJson(ex.getMessage())); + } else { + return super.handle(ex); + } + } + +} 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 22f7c17cd..5e56dba62 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 @@ -23,9 +23,9 @@ import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; -import javax.servlet.Servlet; -import javax.servlet.ServletRegistration; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.Servlet; +import jakarta.servlet.ServletRegistration; +import jakarta.servlet.http.HttpServletRequest; import java.util.concurrent.CountDownLatch; 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 b1f35c809..ba6cbba33 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 @@ -13,12 +13,13 @@ package com.amazonaws.serverless.proxy.spring; import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.ExceptionHandler; import com.amazonaws.serverless.proxy.internal.servlet.ServletLambdaContainerHandlerBuilder; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; public class SpringProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< RequestType, @@ -86,4 +87,9 @@ public SpringLambdaContainerHandler buildAndIniti initializationWrapper.start(handler); return handler; } + + @Override + protected ExceptionHandler defaultExceptionHandler() { + return new SpringAwsProxyExceptionHandler(); + } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/AsyncAppTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/AsyncAppTest.java new file mode 100644 index 000000000..8265e1bf4 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/AsyncAppTest.java @@ -0,0 +1,46 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.springapp.LambdaHandler; +import com.amazonaws.serverless.proxy.spring.springapp.MessageController; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class AsyncAppTest { + + private static LambdaHandler handler; + + @BeforeAll + public static void setUp() { + try { + handler = new LambdaHandler(); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + fail(); + } + } + + @Test + void springApp_helloRequest_returnsCorrect() { + AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyResponse resp = handler.handleRequest(req, new MockLambdaContext()); + assertEquals(200, resp.getStatusCode()); + assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + } + + @Test + void springApp_asyncRequest_returnsCorrect() { + AwsProxyRequest req = new AwsProxyRequestBuilder("/async", "GET").build(); + AwsProxyResponse resp = handler.handleRequest(req, new MockLambdaContext()); + assertEquals(200, resp.getStatusCode()); + assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + } + +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java index 0844b6246..a33d5374f 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java @@ -8,19 +8,17 @@ import com.amazonaws.serverless.proxy.spring.springslowapp.LambdaHandler; import com.amazonaws.serverless.proxy.spring.springslowapp.MessageController; import com.amazonaws.serverless.proxy.spring.springslowapp.SlowAppConfig; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.time.Instant; import java.util.Objects; -import static org.junit.Assert.*; -import static org.junit.Assume.assumeFalse; +import static org.junit.jupiter.api.Assertions.*; public class SlowAppTest { @Test - public void springSlowApp_continuesInBackgroundThread_returnsCorrect() { + void springSlowApp_continuesInBackgroundThread_returnsCorrect() { LambdaHandler slowApp = null; try { slowApp = new LambdaHandler(); @@ -36,7 +34,7 @@ public void springSlowApp_continuesInBackgroundThread_returnsCorrect() { long endRequestTime = Instant.now().toEpochMilli(); assertTrue(endRequestTime - startRequestTime > SlowAppConfig.SlowDownInit.INIT_SLEEP_TIME_MS - 10_000); assertEquals(200, resp.getStatusCode()); - Assert.assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyExceptionHandlerTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyExceptionHandlerTest.java new file mode 100644 index 000000000..5f4a14152 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyExceptionHandlerTest.java @@ -0,0 +1,21 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import jakarta.ws.rs.core.Response; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.web.servlet.NoHandlerFoundException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SpringAwsProxyExceptionHandlerTest { + + @Test + void noHandlerFoundExceptionResultsIn404() { + AwsProxyResponse response = new SpringAwsProxyExceptionHandler(). + handle(new NoHandlerFoundException(HttpMethod.GET.name(), "https://atesturl", + HttpHeaders.EMPTY)); + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatusCode()); + } +} 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 c1365a5b1..9504b6166 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 @@ -18,23 +18,15 @@ 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; -import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.web.WebAppConfiguration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; 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 jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterRegistration; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; import java.io.IOException; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -44,11 +36,10 @@ 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; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; -@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"; @@ -65,19 +56,15 @@ public class SpringAwsProxyTest { // 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); }); private String type; - @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); + return Arrays.asList(new Object[]{"API_GW", "ALB", "HTTP_API"}); } - public SpringAwsProxyTest(String reqType) { + public void initSpringAwsProxyTest(String reqType) { type = reqType; } @@ -112,16 +99,18 @@ private AwsProxyResponse executeRequest(AwsProxyRequestBuilder requestBuilder, C } } - @Before + @BeforeEach public void clearServletContextCache() { AwsServletContext.clearServletContextCache(); } - @Test - public void controllerAdvice_invalidPath_returnAdvice() { + @MethodSource("data") + @ParameterizedTest + void controllerAdvice_invalidPath_returnAdvice(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo2", "GET") - .json() - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); + .json() + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); AwsProxyResponse output = executeRequest(request, lambdaContext); assertNotNull(output); @@ -130,8 +119,10 @@ public void controllerAdvice_invalidPath_returnAdvice() { } - @Test - public void headers_getHeaders_echo() { + @MethodSource("data") + @ParameterizedTest + void headers_getHeaders_echo(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/headers", "GET") .json() .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); @@ -143,8 +134,10 @@ public void headers_getHeaders_echo() { validateMapResponseModel(output); } - @Test - public void headers_servletRequest_echo() { + @MethodSource("data") + @ParameterizedTest + void headers_servletRequest_echo(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/servlet-headers", "GET") .json() .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); @@ -156,8 +149,10 @@ public void headers_servletRequest_echo() { validateMapResponseModel(output); } - @Test - public void queryString_uriInfo_echo() { + @MethodSource("data") + @ParameterizedTest + void queryString_uriInfo_echo(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/query-string", "GET") .json() .queryString(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); @@ -169,11 +164,13 @@ public void queryString_uriInfo_echo() { validateMapResponseModel(output); } - @Test - public void queryString_listParameter_expectCorrectLength() { + @MethodSource("data") + @ParameterizedTest + void queryString_listParameter_expectCorrectLength(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/list-query-string", "GET") - .json() - .queryString("list", "v1,v2,v3"); + .json() + .queryString("list", "v1,v2,v3"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -181,13 +178,15 @@ public void queryString_listParameter_expectCorrectLength() { validateSingleValueModel(output, "3"); } - @Test - public void queryString_multiParam_expectCorrectValueCount() + @MethodSource("data") + @ParameterizedTest + void queryString_multiParam_expectCorrectValueCount(String reqType) throws IOException { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/multivalue-query-string", "GET") - .json() - .queryString("multiple", "first") - .queryString("multiple", "second"); + .json() + .queryString("multiple", "first") + .queryString("multiple", "second"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -198,36 +197,42 @@ public void queryString_multiParam_expectCorrectValueCount() assertTrue(response.getValues().containsKey("second")); } - @Test - public void dateHeader_notModified_expect304() { + @MethodSource("data") + @ParameterizedTest + void dateHeader_notModified_expect304(String reqType) { + initSpringAwsProxyTest(reqType); 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)) - ); + .json() + .header( + HttpHeaders.IF_MODIFIED_SINCE, + DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now().minus(1, ChronoUnit.SECONDS)) + ); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(304, output.getStatusCode()); assertEquals("", output.getBody()); } - @Test - public void dateHeader_notModified_expect200() { + @MethodSource("data") + @ParameterizedTest + void dateHeader_notModified_expect200(String reqType) { + initSpringAwsProxyTest(reqType); 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)) - ); + .json() + .header( + HttpHeaders.IF_MODIFIED_SINCE, + DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now().minus(5, ChronoUnit.DAYS)) + ); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertEquals(EchoResource.STRING_BODY, output.getBody()); } - @Test - public void authorizer_securityContext_customPrincipalSuccess() { + @MethodSource("data") + @ParameterizedTest + void authorizer_securityContext_customPrincipalSuccess(String reqType) { + initSpringAwsProxyTest(reqType); assumeTrue("API_GW".equals(type)); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/authorizer-principal", "GET") .json() @@ -240,16 +245,20 @@ public void authorizer_securityContext_customPrincipalSuccess() { validateSingleValueModel(output, AUTHORIZER_PRINCIPAL_ID); } - @Test - public void errors_unknownRoute_expect404() { + @MethodSource("data") + @ParameterizedTest + void errors_unknownRoute_expect404(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/test33", "GET"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(404, output.getStatusCode()); } - @Test - public void error_contentType_invalidContentType() { + @MethodSource("data") + @ParameterizedTest + void error_contentType_invalidContentType(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/json-body", "POST") .header("Content-Type", "application/octet-stream") .body("asdasdasd"); @@ -258,8 +267,10 @@ public void error_contentType_invalidContentType() { assertEquals(415, output.getStatusCode()); } - @Test - public void error_statusCode_methodNotAllowed() { + @MethodSource("data") + @ParameterizedTest + void error_statusCode_methodNotAllowed(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/status-code", "POST") .json() .queryString("status", "201"); @@ -268,19 +279,23 @@ public void error_statusCode_methodNotAllowed() { assertEquals(405, output.getStatusCode()); } - @Test - public void error_unauthenticatedCall_filterStepsRequest() { + @MethodSource("data") + @ParameterizedTest + void error_unauthenticatedCall_filterStepsRequest(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/status-code", "GET") - .header(UnauthenticatedFilter.HEADER_NAME, "1") - .json() - .queryString("status", "201"); + .header(UnauthenticatedFilter.HEADER_NAME, "1") + .json() + .queryString("status", "201"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(401, output.getStatusCode()); } - @Test - public void responseBody_responseWriter_validBody() throws JsonProcessingException { + @MethodSource("data") + @ParameterizedTest + void responseBody_responseWriter_validBody(String reqType) throws JsonProcessingException { + initSpringAwsProxyTest(reqType); SingleValueModel singleValueModel = new SingleValueModel(); singleValueModel.setValue(CUSTOM_HEADER_VALUE); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/json-body", "POST") @@ -294,13 +309,15 @@ public void responseBody_responseWriter_validBody() throws JsonProcessingExcepti validateSingleValueModel(output, CUSTOM_HEADER_VALUE); } - @Test - public void responseBody_responseWriter_validBody_UTF() throws JsonProcessingException { + @MethodSource("data") + @ParameterizedTest + void responseBody_responseWriter_validBody_UTF(String reqType) throws JsonProcessingException { + initSpringAwsProxyTest(reqType); SingleValueModel singleValueModel = new SingleValueModel(); singleValueModel.setValue(UNICODE_VALUE); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/json-body", "POST") - .header("Content-Type", "application/json; charset=UTF-8") - .body(objectMapper.writeValueAsString(singleValueModel)); + .header("Content-Type", "application/json; charset=UTF-8") + .body(objectMapper.writeValueAsString(singleValueModel)); LambdaContainerHandler.getContainerConfig().setDefaultContentCharset("UTF-8"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -309,8 +326,10 @@ public void responseBody_responseWriter_validBody_UTF() throws JsonProcessingExc LambdaContainerHandler.getContainerConfig().setDefaultContentCharset(ContainerConfig.DEFAULT_CONTENT_CHARSET); } - @Test - public void statusCode_responseStatusCode_customStatusCode() { + @MethodSource("data") + @ParameterizedTest + void statusCode_responseStatusCode_customStatusCode(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/status-code", "GET") .json() .queryString("status", "201"); @@ -319,8 +338,10 @@ public void statusCode_responseStatusCode_customStatusCode() { assertEquals(201, output.getStatusCode()); } - @Test - public void base64_binaryResponse_base64Encoding() { + @MethodSource("data") + @ParameterizedTest + void base64_binaryResponse_base64Encoding(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/binary", "GET"); AwsProxyResponse response = executeRequest(request, lambdaContext); @@ -328,11 +349,13 @@ public void base64_binaryResponse_base64Encoding() { assertTrue(Base64.isBase64(response.getBody())); } - @Test - public void injectBody_populatedResponse_noException() { + @MethodSource("data") + @ParameterizedTest + void injectBody_populatedResponse_noException(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/request-body", "POST") - .header(HttpHeaders.CONTENT_TYPE, "text/plain") - .body("This is a populated body"); + .header(HttpHeaders.CONTENT_TYPE, "text/plain") + .body("This is a populated body"); AwsProxyResponse response = executeRequest(request, lambdaContext); assertNotNull(response.getBody()); @@ -349,15 +372,17 @@ public void injectBody_populatedResponse_noException() { AwsProxyResponse emptyResp = executeRequest(emptyReq, lambdaContext); try { SingleValueModel output = objectMapper.readValue(emptyResp.getBody(), SingleValueModel.class); - assertEquals(null, output.getValue()); + assertNull(output.getValue()); } catch (IOException e) { e.printStackTrace(); fail(); } } - @Test - public void servletRequestEncoding_acceptEncoding_okStatusCode() { + @MethodSource("data") + @ParameterizedTest + void servletRequestEncoding_acceptEncoding_okStatusCode(String reqType) { + initSpringAwsProxyTest(reqType); SingleValueModel singleValueModel = new SingleValueModel(); singleValueModel.setValue(CUSTOM_HEADER_VALUE); AwsProxyRequestBuilder request = null; @@ -375,8 +400,10 @@ public void servletRequestEncoding_acceptEncoding_okStatusCode() { assertEquals(200, output.getStatusCode()); } - @Test - public void request_requestURI() { + @MethodSource("data") + @ParameterizedTest + void request_requestURI(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/request-URI", "GET"); AwsProxyResponse output = executeRequest(request, lambdaContext); @@ -385,8 +412,10 @@ public void request_requestURI() { validateSingleValueModel(output, "/echo/request-URI"); } - @Test - public void request_requestURL() { + @MethodSource("data") + @ParameterizedTest + void request_requestURL(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/request-url", "GET") .scheme("https") .serverName("api.myserver.com") @@ -398,12 +427,14 @@ public void request_requestURL() { validateSingleValueModel(output, "https://api.myserver.com/echo/request-url"); } - @Test - public void request_encodedPath_returnsDecodedPath() { + @MethodSource("data") + @ParameterizedTest + void request_encodedPath_returnsDecodedPath(String reqType) { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/encoded-request-uri/Some%20Thing", "GET") - .scheme("https") - .serverName("api.myserver.com") - .stage("prod"); + .scheme("https") + .serverName("api.myserver.com") + .stage("prod"); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); @@ -412,13 +443,15 @@ public void request_encodedPath_returnsDecodedPath() { } - @Test - public void contextPath_generateLink_returnsCorrectPath() { + @MethodSource("data") + @ParameterizedTest + void contextPath_generateLink_returnsCorrectPath(String reqType) { + initSpringAwsProxyTest(reqType); assumeFalse("ALB".equals(type)); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/generate-uri", "GET") - .scheme("https") - .serverName("api.myserver.com") - .stage("prod"); + .scheme("https") + .serverName("api.myserver.com") + .stage("prod"); LambdaContainerHandler.getContainerConfig().addCustomDomain("api.myserver.com"); SpringLambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(true); @@ -432,11 +465,13 @@ public void contextPath_generateLink_returnsCorrectPath() { SpringLambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(false); } - @Test - public void multipart_getFileName_returnsCorrectFileName() + @MethodSource("data") + @ParameterizedTest + void multipart_getFileName_returnsCorrectFileName(String reqType) throws IOException { + initSpringAwsProxyTest(reqType); AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo/attachment", "POST") - .formFilePart("testFile", "myFile.txt", "hello".getBytes()); + .formFilePart("testFile", "myFile.txt", "hello".getBytes()); AwsProxyResponse output = executeRequest(request, lambdaContext); assertEquals(200, output.getStatusCode()); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringServletContextTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringServletContextTest.java index 15e9c5b4e..11b6c7c5f 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringServletContextTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringServletContextTest.java @@ -11,18 +11,18 @@ import com.amazonaws.serverless.proxy.spring.echoapp.CustomHeaderFilter; import com.amazonaws.serverless.proxy.spring.echoapp.EchoSpringAppConfig; import com.amazonaws.serverless.proxy.spring.echoapp.model.ValidatedUserModel; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import javax.servlet.DispatcherType; -import javax.servlet.FilterRegistration; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterRegistration; import java.util.EnumSet; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; // we don't use the spring annotations to pretend we are running in the actual container public class SpringServletContextTest { @@ -31,7 +31,7 @@ public class SpringServletContextTest { private static SpringLambdaContainerHandler handler; - @BeforeClass + @BeforeAll public static void setUp() { try { handler = SpringLambdaContainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class); @@ -49,7 +49,7 @@ public static void setUp() { } @Test - public void context_autowireValidContext_echoContext() { + void context_autowireValidContext_echoContext() { AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/servlet-context", "GET") .json() .stage(STAGE) @@ -62,7 +62,7 @@ public void context_autowireValidContext_echoContext() { } @Test - public void context_contextAware_contextEcho() { + void context_contextAware_contextEcho() { AwsProxyRequest request = new AwsProxyRequestBuilder("/context/echo", "GET") .json() .stage(STAGE) @@ -75,7 +75,7 @@ public void context_contextAware_contextEcho() { } @Test - public void filter_customHeaderFilter_echoHeaders() { + void filter_customHeaderFilter_echoHeaders() { AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/headers", "GET") .json() .stage(STAGE) @@ -89,7 +89,7 @@ public void filter_customHeaderFilter_echoHeaders() { } @Test - public void filter_validationFilter_emptyName() { + void filter_validationFilter_emptyName() { ValidatedUserModel userModel = new ValidatedUserModel(); userModel.setFirstName("Test"); AwsProxyRequest request = new AwsProxyRequestBuilder("/context/user", "POST") @@ -102,11 +102,11 @@ public void filter_validationFilter_emptyName() { } @Test - public void exception_populatedException_annotationValuesMappedCorrectly() { + void exception_populatedException_annotationValuesMappedCorrectly() { AwsProxyRequest request = new AwsProxyRequestBuilder("/context/exception", "GET") - .stage(STAGE) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) - .build(); + .stage(STAGE) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); AwsProxyResponse output = handler.proxy(request, lambdaContext); @@ -115,11 +115,11 @@ public void exception_populatedException_annotationValuesMappedCorrectly() { } @Test - public void cookie_injectInResponse_expectCustomSetCookie() { + void cookie_injectInResponse_expectCustomSetCookie() { AwsProxyRequest request = new AwsProxyRequestBuilder("/context/cookie", "GET") - .stage(STAGE) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) - .build(); + .stage(STAGE) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); AwsProxyResponse output = handler.proxy(request, lambdaContext); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/StaticAppProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/StaticAppProxyTest.java index 38d3e3d22..9e828119f 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/StaticAppProxyTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/StaticAppProxyTest.java @@ -8,12 +8,13 @@ import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.serverless.proxy.spring.staticapp.LambdaHandler; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MediaType; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; public class StaticAppProxyTest { @@ -21,7 +22,7 @@ public class StaticAppProxyTest { private LambdaHandler lambdaHandler = new LambdaHandler(); @Test - public void staticPage() { + void staticPage() { AwsProxyRequest req = new AwsProxyRequestBuilder("/sample/page", "GET").build(); // we temporarily allow the container to read from any path LambdaContainerHandler.getContainerConfig().addValidFilePath("/"); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/ContextResource.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/ContextResource.java index d52237566..8f7eaf2a0 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/ContextResource.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/ContextResource.java @@ -17,13 +17,13 @@ import org.springframework.web.context.ServletContextAware; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import javax.servlet.ServletContext; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.validation.Valid; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation; diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/CustomHeaderFilter.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/CustomHeaderFilter.java index ca0030439..f6427db65 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/CustomHeaderFilter.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/CustomHeaderFilter.java @@ -1,13 +1,13 @@ package com.amazonaws.serverless.proxy.spring.echoapp; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java index ceb358bea..a6dd033a0 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java @@ -12,11 +12,11 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartResolver; -import org.springframework.web.multipart.commons.CommonsMultipartResolver; +import org.springframework.web.multipart.support.StandardServletMultipartResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.net.URI; @@ -42,8 +42,8 @@ public class EchoResource { @Bean public MultipartResolver multipartResolver() { - CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); - multipartResolver.setMaxUploadSize(1000000); + MultipartResolver multipartResolver = new StandardServletMultipartResolver(); + //multipartResolver.setMaxUploadSize(1000000); return multipartResolver; } 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 a4d5f069c..cf4aa495e 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 @@ -24,7 +24,7 @@ public MockLambdaContext lambdaContext() { } @Bean - public javax.validation.Validator localValidatorFactoryBean() { + public jakarta.validation.Validator localValidatorFactoryBean() { return new LocalValidatorFactoryBean(); } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java index ba16d905f..ac88119e0 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java @@ -1,14 +1,14 @@ package com.amazonaws.serverless.proxy.spring.echoapp; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/ValidatedUserModel.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/ValidatedUserModel.java index 56c157e83..c587429da 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/ValidatedUserModel.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/ValidatedUserModel.java @@ -1,9 +1,8 @@ package com.amazonaws.serverless.proxy.spring.echoapp.model; -import org.hibernate.validator.constraints.Email; -import org.hibernate.validator.constraints.NotEmpty; - -import javax.validation.constraints.Size; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; public class ValidatedUserModel { @NotEmpty diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomServlet.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomServlet.java index 2b8604d3a..5c344b4d5 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomServlet.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomServlet.java @@ -2,9 +2,9 @@ import org.springframework.context.ApplicationContext; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; public class CustomServlet extends HttpServlet { diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomServletTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomServletTest.java index 9cac0aa1f..5959a8110 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomServletTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomServletTest.java @@ -2,26 +2,26 @@ import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import javax.ws.rs.HttpMethod; +import jakarta.ws.rs.HttpMethod; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; public class CustomServletTest { @Test - public void customServlet() throws IOException { + void customServlet() throws IOException { StreamLambdaHandler lambdaHandler = new StreamLambdaHandler(); InputStream requestStream = new AwsProxyRequestBuilder("/test", HttpMethod.GET) .buildStream(); ByteArrayOutputStream responseStream = new ByteArrayOutputStream(); lambdaHandler.handleRequest(requestStream, responseStream, new MockLambdaContext()); - assertTrue("response should contain value set in CustomServlet", - responseStream.toString().contains("Unittest")); + assertTrue(responseStream.toString().contains("Unittest"), + "response should contain value set in CustomServlet"); } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomSpringLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomSpringLambdaContainerHandler.java index 3ed872330..4e5fe2783 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomSpringLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/extensibility/CustomSpringLambdaContainerHandler.java @@ -9,8 +9,8 @@ import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler; import org.springframework.web.context.ConfigurableWebApplicationContext; -import javax.servlet.ServletRegistration; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.ServletRegistration; +import jakarta.servlet.http.HttpServletRequest; public class CustomSpringLambdaContainerHandler extends SpringLambdaContainerHandler { diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java index 727a4cdc4..7742db7f6 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java @@ -9,19 +9,19 @@ import com.amazonaws.serverless.proxy.spring.echoapp.EchoSpringAppConfig; import com.amazonaws.serverless.proxy.spring.echoapp.model.MapResponseModel; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.web.WebAppConfiguration; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = EchoSpringAppConfig.class) @WebAppConfiguration @TestExecutionListeners(inheritListeners = false, listeners = {DependencyInjectionTestExecutionListener.class}) @@ -32,13 +32,13 @@ public class SpringProfileTest { @Autowired private MockLambdaContext lambdaContext; - @Before + @BeforeEach public void clearServletContextCache() { AwsServletContext.clearServletContextCache(); } @Test - public void profile_defaultProfile() throws Exception { + void profile_defaultProfile() throws Exception { AwsProxyRequest request = new AwsProxyRequestBuilder("/profile/spring-properties", "GET") .build(); @@ -54,7 +54,7 @@ public void profile_defaultProfile() throws Exception { } @Test - public void profile_overrideProfile() throws Exception { + void profile_overrideProfile() throws Exception { AwsProxyRequest request = new AwsProxyRequestBuilder("/profile/spring-properties", "GET") .build(); SpringLambdaContainerHandler handler = SpringLambdaContainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springapp/AppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springapp/AppConfig.java new file mode 100644 index 000000000..d3562c221 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springapp/AppConfig.java @@ -0,0 +1,8 @@ +package com.amazonaws.serverless.proxy.spring.springapp; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import({MessageController.class}) +public class AppConfig { } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springapp/LambdaHandler.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springapp/LambdaHandler.java new file mode 100644 index 000000000..f0cad13c5 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springapp/LambdaHandler.java @@ -0,0 +1,26 @@ +package com.amazonaws.serverless.proxy.spring.springapp; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler; +import com.amazonaws.serverless.proxy.spring.SpringProxyHandlerBuilder; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +public class LambdaHandler implements RequestHandler { + private SpringLambdaContainerHandler handler; + + public LambdaHandler() throws ContainerInitializationException { + handler = new SpringProxyHandlerBuilder() + .defaultProxy() + .asyncInit() + .configurationClasses(AppConfig.class) + .buildAndInitialize(); + } + + @Override + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + return handler.proxy(awsProxyRequest, context); + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springapp/MessageController.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springapp/MessageController.java new file mode 100644 index 000000000..1f5dc83d4 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springapp/MessageController.java @@ -0,0 +1,25 @@ +package com.amazonaws.serverless.proxy.spring.springapp; + +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.context.request.async.DeferredResult; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +@RestController +@EnableWebMvc +public class MessageController { + public static final String HELLO_MESSAGE = "Hello"; + + @RequestMapping(path="/hello", method= RequestMethod.GET) + public String hello() { + return HELLO_MESSAGE; + } + + @RequestMapping(path="/async", method= RequestMethod.GET) + public DeferredResult asyncHello() { + DeferredResult result = new DeferredResult<>(); + result.setResult(HELLO_MESSAGE); + return result; + } +} 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 935954d0f..0607c6f17 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,6 @@ public LambdaHandler() throws ContainerInitializationException { long startTime = Instant.now().toEpochMilli(); handler = new SpringProxyHandlerBuilder() .defaultProxy() - .asyncInit() .configurationClasses(SlowAppConfig.class) .buildAndInitialize(); constructorTime = Instant.now().toEpochMilli() - startTime; diff --git a/aws-serverless-java-container-springboot2/pom.xml b/aws-serverless-java-container-springboot3/pom.xml similarity index 67% rename from aws-serverless-java-container-springboot2/pom.xml rename to aws-serverless-java-container-springboot3/pom.xml index 8768c373c..dc9ffca03 100644 --- a/aws-serverless-java-container-springboot2/pom.xml +++ b/aws-serverless-java-container-springboot3/pom.xml @@ -3,33 +3,43 @@ aws-serverless-java-container com.amazonaws.serverless - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT 4.0.0 com.amazonaws.serverless - aws-serverless-java-container-springboot2 - AWS Serverless Java container support - SpringBoot 2 implementation - Allows Java applications written for SpringBoot 2 to run in AWS Lambda + aws-serverless-java-container-springboot3 + AWS Serverless Java container support - SpringBoot 3 implementation + Allows Java applications written for SpringBoot 3 to run in AWS Lambda https://aws.amazon.com/lambda - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT - 5.3.23 - 2.7.5 - 5.7.4 - 1.8 - 1.8 + 6.2.8 + 3.5.8 + 6.4.5 + + org.springframework.cloud + spring-cloud-function-serverless-web + 4.1.5 + + + com.amazonaws.serverless + aws-serverless-java-container-core + 2.1.5-SNAPSHOT + com.amazonaws.serverless aws-serverless-java-container-core - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT + tests + test-jar + test - org.springframework spring-webflux @@ -147,25 +157,80 @@ test + + org.hibernate.validator + hibernate-validator + 8.0.2.Final + test + + + + org.junit.jupiter + junit-jupiter + test + + jakarta.validation jakarta.validation-api - 2.0.2 + 3.1.1 test + - org.hibernate.validator - hibernate-validator - 6.2.1.Final + jakarta.websocket + jakarta.websocket-api + 2.2.0 + test + + + + jakarta.websocket + jakarta.websocket-client-api + 2.2.0 + test + + + + org.springframework.boot + spring-boot-starter-data-jpa + ${springboot.version} test + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.apache.tomcat.embed + tomcat-embed-core + + + org.apache.tomcat.embed + tomcat-embed-websocket + + - - javax.activation - activation - 1.1.1 + com.h2database + h2 + 2.3.232 test + + @@ -176,6 +241,11 @@ ${basedir}/target/coverage-reports/jacoco-unit.exec ${basedir}/target/coverage-reports/jacoco-unit.exec + + + com/amazonaws/serverless/proxy/spring/AwsSpringWebCustomRuntimeEventLoop* + com/amazonaws/serverless/proxy/spring/AwsSpringAotTypesProcessor* + @@ -219,9 +289,8 @@ org.apache.maven.plugins maven-surefire-plugin - 2.9 - always + false @@ -252,14 +321,33 @@ 7 false - - - - check - - - + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringAotTypesProcessor.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringAotTypesProcessor.java new file mode 100644 index 000000000..87e9ead04 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringAotTypesProcessor.java @@ -0,0 +1,97 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.proxy.model.*; +import org.springframework.aot.generate.GenerationContext; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; +import com.fasterxml.jackson.core.JsonToken; + +/** + * AOT Initialization processor required to register reflective hints for GraalVM. + * This is necessary to ensure proper JSON serialization/deserialization. + * It is registered with META-INF/spring/aot.factories + * + * @author Oleg Zhurakousky + */ +public class AwsSpringAotTypesProcessor implements BeanFactoryInitializationAotProcessor { + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + return new ReflectiveProcessorBeanFactoryInitializationAotContribution(); + } + + private static final class ReflectiveProcessorBeanFactoryInitializationAotContribution implements BeanFactoryInitializationAotContribution { + @Override + public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { + RuntimeHints runtimeHints = generationContext.getRuntimeHints(); + // known static types + + runtimeHints.reflection().registerType(AwsProxyRequest.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES); + runtimeHints.reflection().registerType(AwsProxyResponse.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES); + runtimeHints.reflection().registerType(SingleValueHeaders.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES); + runtimeHints.reflection().registerType(JsonToken.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES); + runtimeHints.reflection().registerType(MultiValuedTreeMap.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES); + runtimeHints.reflection().registerType(Headers.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES); + runtimeHints.reflection().registerType(AwsProxyRequestContext.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES); + runtimeHints.reflection().registerType(ApiGatewayRequestIdentity.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES); + runtimeHints.reflection().registerType(AwsHttpServletResponse.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS); + runtimeHints.reflection().registerType(HttpApiV2ProxyRequest.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS); + runtimeHints.reflection().registerType(HttpApiV2HttpContext.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS); + runtimeHints.reflection().registerType(HttpApiV2ProxyRequestContext.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS); + runtimeHints.reflection().registerType(HttpApiV2AuthorizerMap.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS); + runtimeHints.reflection().registerType(HttpApiV2AuthorizerMap.HttpApiV2AuthorizerDeserializer.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS); + runtimeHints.reflection().registerType(HttpApiV2AuthorizerMap.HttpApiV2AuthorizerSerializer.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS); + runtimeHints.reflection().registerType(HttpApiV2IamAuthorizer.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS); + runtimeHints.reflection().registerType(HttpApiV2JwtAuthorizer.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS, MemberCategory.DECLARED_CLASSES, MemberCategory.INTROSPECT_DECLARED_METHODS); + } + + } +} diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java new file mode 100644 index 000000000..0f6270b39 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java @@ -0,0 +1,244 @@ +package com.amazonaws.serverless.proxy.spring; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import com.amazonaws.serverless.proxy.internal.HttpUtils; +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest; +import com.amazonaws.serverless.proxy.model.RequestSource; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.function.serverless.web.ServerlessHttpServletRequest; +import org.springframework.cloud.function.serverless.web.ServerlessMVC; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.CollectionUtils; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.MultiValueMapAdapter; +import org.springframework.util.StringUtils; + +import com.amazonaws.serverless.proxy.AwsHttpApiV2SecurityContextWriter; +import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.SecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; + +import static com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest.decodeValueIfEncoded; +import static com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest.getQueryParamValuesAsList; + +class AwsSpringHttpProcessingUtils { + + private static Log logger = LogFactory.getLog(AwsSpringHttpProcessingUtils.class); + private static final int LAMBDA_MAX_REQUEST_DURATION_MINUTES = 15; + + private AwsSpringHttpProcessingUtils() { + + } + + public static AwsProxyResponse processRequest(HttpServletRequest request, ServerlessMVC mvc, + AwsProxyHttpServletResponseWriter responseWriter) { + CountDownLatch latch = new CountDownLatch(1); + AwsHttpServletResponse response = new AwsHttpServletResponse(request, latch); + try { + mvc.service(request, response); + boolean requestTimedOut = !latch.await(LAMBDA_MAX_REQUEST_DURATION_MINUTES, TimeUnit.MINUTES); // timeout is potentially lower as user configures it + if (requestTimedOut) { + logger.warn("request timed out after " + LAMBDA_MAX_REQUEST_DURATION_MINUTES + " minutes"); + } + AwsProxyResponse awsResponse = responseWriter.writeResponse(response, null); + return awsResponse; + } + catch (Exception e) { + e.printStackTrace(); + throw new IllegalStateException(e); + } + } + + public static String extractVersion() { + try { + String path = AwsSpringHttpProcessingUtils.class.getProtectionDomain().getCodeSource().getLocation().toString(); + int endIndex = path.lastIndexOf('.'); + if (endIndex < 0) { + return "UNKNOWN-VERSION"; + } + int startIndex = path.lastIndexOf("/") + 1; + return path.substring(startIndex, endIndex).replace("spring-cloud-function-serverless-web-", ""); + } + catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Failed to detect version", e); + } + return "UNKNOWN-VERSION"; + } + + } + + public static HttpServletRequest generateHttpServletRequest(InputStream jsonRequest, Context lambdaContext, + ServletContext servletContext, ObjectMapper mapper) { + try { + String text = new String(FileCopyUtils.copyToByteArray(jsonRequest), StandardCharsets.UTF_8); + if (logger.isDebugEnabled()) { + logger.debug("Creating HttpServletRequest from: " + text); + } + return generateHttpServletRequest(text, lambdaContext, servletContext, mapper); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static HttpServletRequest generateHttpServletRequest(String jsonRequest, Context lambdaContext, + ServletContext servletContext, ObjectMapper mapper) { + Map _request = readValue(jsonRequest, Map.class, mapper); + SecurityContextWriter securityWriter = "2.0".equals(_request.get("version")) + ? new AwsHttpApiV2SecurityContextWriter() + : new AwsProxySecurityContextWriter(); + HttpServletRequest httpServletRequest = "2.0".equals(_request.get("version")) + ? AwsSpringHttpProcessingUtils.generateRequest2(jsonRequest, lambdaContext, securityWriter, mapper, servletContext) + : AwsSpringHttpProcessingUtils.generateRequest1(jsonRequest, lambdaContext, securityWriter, mapper, servletContext); + return httpServletRequest; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static HttpServletRequest generateRequest1(String request, Context lambdaContext, + SecurityContextWriter securityWriter, ObjectMapper mapper, ServletContext servletContext) { + AwsProxyRequest v1Request = readValue(request, AwsProxyRequest.class, mapper); + + ServerlessHttpServletRequest httpRequest = new ServerlessHttpServletRequest(servletContext, v1Request.getHttpMethod(), v1Request.getPath()); + + populateQueryStringParametersV1(v1Request, httpRequest); + populateMultiValueQueryStringParametersV1(v1Request, httpRequest); + + if (v1Request.getMultiValueHeaders() != null) { + MultiValueMapAdapter headers = new MultiValueMapAdapter(v1Request.getMultiValueHeaders()); + httpRequest.setHeaders(headers); + } + populateContentAndContentType( + v1Request.getBody(), + v1Request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE), + v1Request.isBase64Encoded(), + httpRequest + ); + if (v1Request.getRequestContext() != null) { + httpRequest.setAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY, v1Request.getRequestContext()); + httpRequest.setAttribute(RequestReader.ALB_CONTEXT_PROPERTY, v1Request.getRequestContext().getElb()); + } + httpRequest.setAttribute(RequestReader.API_GATEWAY_STAGE_VARS_PROPERTY, v1Request.getStageVariables()); + httpRequest.setAttribute(RequestReader.API_GATEWAY_EVENT_PROPERTY, v1Request); + httpRequest.setAttribute(RequestReader.LAMBDA_CONTEXT_PROPERTY, lambdaContext); + httpRequest.setAttribute(RequestReader.JAX_SECURITY_CONTEXT_PROPERTY, + securityWriter.writeSecurityContext(v1Request, lambdaContext)); + return httpRequest; + } + + + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static HttpServletRequest generateRequest2(String request, Context lambdaContext, + SecurityContextWriter securityWriter, ObjectMapper mapper, ServletContext servletContext) { + HttpApiV2ProxyRequest v2Request = readValue(request, HttpApiV2ProxyRequest.class, mapper); + + + ServerlessHttpServletRequest httpRequest = new ServerlessHttpServletRequest(servletContext, + v2Request.getRequestContext().getHttp().getMethod(), v2Request.getRequestContext().getHttp().getPath()); + populateQueryStringParametersV2(v2Request.getQueryStringParameters(), httpRequest); + + v2Request.getHeaders().forEach(httpRequest::setHeader); + + populateContentAndContentType( + v2Request.getBody(), + v2Request.getHeaders().get(HttpHeaders.CONTENT_TYPE), + v2Request.isBase64Encoded(), + httpRequest + ); + + httpRequest.setAttribute(RequestReader.HTTP_API_CONTEXT_PROPERTY, v2Request.getRequestContext()); + httpRequest.setAttribute(RequestReader.HTTP_API_STAGE_VARS_PROPERTY, v2Request.getStageVariables()); + httpRequest.setAttribute(RequestReader.HTTP_API_EVENT_PROPERTY, v2Request); + httpRequest.setAttribute(RequestReader.LAMBDA_CONTEXT_PROPERTY, lambdaContext); + httpRequest.setAttribute(RequestReader.JAX_SECURITY_CONTEXT_PROPERTY, + securityWriter.writeSecurityContext(v2Request, lambdaContext)); + return httpRequest; + } + + private static void populateQueryStringParametersV2(Map requestParameters, ServerlessHttpServletRequest httpRequest) { + if (!CollectionUtils.isEmpty(requestParameters)) { + for (Entry entry : requestParameters.entrySet()) { + // fix according to parseRawQueryString + httpRequest.setParameter(entry.getKey(), entry.getValue()); + } + } + } + + private static void populateQueryStringParametersV1(AwsProxyRequest v1Request, ServerlessHttpServletRequest httpRequest) { + Map requestParameters = v1Request.getQueryStringParameters(); + if (!CollectionUtils.isEmpty(requestParameters)) { + // decode all keys and values in map + for (Entry entry : requestParameters.entrySet()) { + String k = v1Request.getRequestSource() == RequestSource.ALB ? decodeValueIfEncoded(entry.getKey()) : entry.getKey(); + String v = v1Request.getRequestSource() == RequestSource.ALB ? decodeValueIfEncoded(entry.getValue()) : entry.getValue(); + httpRequest.setParameter(k, v); + } + } + } + + private static void populateMultiValueQueryStringParametersV1(AwsProxyRequest v1Request, ServerlessHttpServletRequest httpRequest) { + if (v1Request.getMultiValueQueryStringParameters() != null) { + MultiValueMapAdapter queryStringParameters = new MultiValueMapAdapter<>(v1Request.getMultiValueQueryStringParameters()); + queryStringParameters.forEach((k, v) -> { + String key = v1Request.getRequestSource() == RequestSource.ALB + ? decodeValueIfEncoded(k) + : k; + List value = v1Request.getRequestSource() == RequestSource.ALB + ? getQueryParamValuesAsList(v1Request.getMultiValueQueryStringParameters(), k, false).stream() + .map(AwsHttpServletRequest::decodeValueIfEncoded) + .toList() + : v; + httpRequest.setParameter(key, value.toArray(new String[0])); + }); + } + } + + private static T readValue(String json, Class clazz, ObjectMapper mapper) { + try { + return mapper.readValue(json, clazz); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private static void populateContentAndContentType( + String body, + String contentType, + boolean base64Encoded, + ServerlessHttpServletRequest httpRequest) { + if (StringUtils.hasText(body)) { + httpRequest.setContentType(contentType == null ? MediaType.APPLICATION_JSON_VALUE : contentType); + if (base64Encoded) { + httpRequest.setContent(Base64.getMimeDecoder().decode(body)); + } else { + Charset charseEncoding = HttpUtils.parseCharacterEncoding(contentType,StandardCharsets.UTF_8); + httpRequest.setContent(body.getBytes(charseEncoding)); + } + } + } + + + +} diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebCustomRuntimeEventLoop.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebCustomRuntimeEventLoop.java new file mode 100644 index 000000000..c015a8024 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebCustomRuntimeEventLoop.java @@ -0,0 +1,182 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.serverless.proxy.spring; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URI; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; +import org.springframework.cloud.function.serverless.web.ServerlessMVC; +import org.springframework.context.SmartLifecycle; +import org.springframework.core.env.Environment; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +/** + * Event loop and necessary configurations to support AWS Lambda Custom Runtime + * - https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html. + * + * @author Oleg Zhurakousky + * @author Mark Sailes + * + */ +public final class AwsSpringWebCustomRuntimeEventLoop implements SmartLifecycle { + + private static Log logger = LogFactory.getLog(AwsSpringWebCustomRuntimeEventLoop.class); + + static final String LAMBDA_VERSION_DATE = "2018-06-01"; + private static final String LAMBDA_ERROR_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/{2}/error"; + private static final String LAMBDA_RUNTIME_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/next"; + private static final String LAMBDA_INVOCATION_URL_TEMPLATE = "http://{0}/{1}/runtime/invocation/{2}/response"; + private static final String USER_AGENT_VALUE = String.format("spring-cloud-function/%s-%s", + System.getProperty("java.runtime.version"), AwsSpringHttpProcessingUtils.extractVersion()); + + private final ServletWebServerApplicationContext applicationContext; + + private volatile boolean running; + + private final ExecutorService executor = Executors.newSingleThreadExecutor(); + + public AwsSpringWebCustomRuntimeEventLoop(ServletWebServerApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public void run() { + this.running = true; + this.executor.execute(() -> { + eventLoop(this.applicationContext); + }); + } + + @Override + public void start() { + this.run(); + } + + @Override + public void stop() { + this.executor.shutdownNow(); + this.running = false; + } + + @Override + public boolean isRunning() { + return this.running; + } + + private void eventLoop(ServletWebServerApplicationContext context) { + ServerlessMVC mvc = ServerlessMVC.INSTANCE(context); + + Environment environment = context.getEnvironment(); + logger.info("Starting AWSWebRuntimeEventLoop"); + + String runtimeApi = environment.getProperty("AWS_LAMBDA_RUNTIME_API"); + String eventUri = MessageFormat.format(LAMBDA_RUNTIME_URL_TEMPLATE, runtimeApi, LAMBDA_VERSION_DATE); + if (logger.isDebugEnabled()) { + logger.debug("Event URI: " + eventUri); + } + + RequestEntity requestEntity = RequestEntity.get(URI.create(eventUri)) + .header("User-Agent", USER_AGENT_VALUE).build(); + RestTemplate rest = new RestTemplate(); + ObjectMapper mapper = new ObjectMapper();//.getBean(ObjectMapper.class); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + AwsProxyHttpServletResponseWriter responseWriter = new AwsProxyHttpServletResponseWriter(); + + logger.info("Entering event loop"); + while (this.isRunning()) { + logger.debug("Attempting to get new event"); + ResponseEntity incomingEvent = rest.exchange(requestEntity, String.class); + + if (incomingEvent != null && incomingEvent.hasBody()) { + if (logger.isDebugEnabled()) { + logger.debug("New Event received from AWS Gateway: " + incomingEvent.getBody()); + } + String requestId = incomingEvent.getHeaders().getFirst("Lambda-Runtime-Aws-Request-Id"); + + try { + logger.debug("Submitting request to the user's web application"); + + AwsProxyResponse awsResponse = AwsSpringHttpProcessingUtils.processRequest( + AwsSpringHttpProcessingUtils.generateHttpServletRequest(incomingEvent.getBody(), + null, mvc.getServletContext(), mapper), mvc, responseWriter); + if (logger.isDebugEnabled()) { + logger.debug("Received response - body: " + awsResponse.getBody() + + "; status: " + awsResponse.getStatusCode() + "; headers: " + awsResponse.getHeaders()); + } + + String invocationUrl = MessageFormat.format(LAMBDA_INVOCATION_URL_TEMPLATE, runtimeApi, + LAMBDA_VERSION_DATE, requestId); + + ResponseEntity result = rest.exchange(RequestEntity.post(URI.create(invocationUrl)) + .header("User-Agent", USER_AGENT_VALUE).body(awsResponse), byte[].class); + if (logger.isDebugEnabled()) { + logger.debug("Response sent: body: " + result.getBody() + + "; status: " + result.getStatusCode() + "; headers: " + result.getHeaders()); + } + if (logger.isInfoEnabled()) { + logger.info("Result POST status: " + result); + } + } + catch (Exception e) { + logger.error(e); + this.propagateAwsError(requestId, e, mapper, runtimeApi, rest); + } + } + } + } + + private void propagateAwsError(String requestId, Exception e, ObjectMapper mapper, String runtimeApi, RestTemplate rest) { + String errorMessage = e.getMessage(); + String errorType = e.getClass().getSimpleName(); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + String stackTrace = sw.toString(); + Map em = new HashMap<>(); + em.put("errorMessage", errorMessage); + em.put("errorType", errorType); + em.put("stackTrace", stackTrace); + try { + byte[] outputBody = mapper.writeValueAsBytes(em); + String errorUrl = MessageFormat.format(LAMBDA_ERROR_URL_TEMPLATE, runtimeApi, LAMBDA_VERSION_DATE, requestId); + ResponseEntity result = rest.exchange(RequestEntity.post(URI.create(errorUrl)) + .header("User-Agent", USER_AGENT_VALUE) + .body(outputBody), Object.class); + if (logger.isInfoEnabled()) { + logger.info("Result ERROR status: " + result.getStatusCode()); + } + } + catch (Exception e2) { + throw new IllegalArgumentException("Failed to report error", e2); + } + } +} diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebRuntimeInitializer.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebRuntimeInitializer.java new file mode 100644 index 000000000..992bc635e --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebRuntimeInitializer.java @@ -0,0 +1,66 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.serverless.proxy.spring; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.SmartLifecycle; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; + +/** + * Initializer to optionally start Custom Runtime to process web workloads. + * Registered with META-INF/spring.factories + * + * @author Dave Syer + * @author Oleg Zhurakousky + */ +public class AwsSpringWebRuntimeInitializer implements ApplicationContextInitializer { + + private static Log logger = LogFactory.getLog(AwsSpringWebRuntimeInitializer.class); + + @Override + public void initialize(GenericApplicationContext context) { + Environment environment = context.getEnvironment(); + + if (context instanceof ServletWebServerApplicationContext && isCustomRuntime(environment)) { + if (context.getBeanFactory().getBeanNamesForType(AwsSpringWebCustomRuntimeEventLoop.class, false, false).length == 0) { + context.registerBean(StringUtils.uncapitalize(AwsSpringWebCustomRuntimeEventLoop.class.getSimpleName()), + SmartLifecycle.class, () -> new AwsSpringWebCustomRuntimeEventLoop((ServletWebServerApplicationContext) context)); + } + } + } + + private boolean isCustomRuntime(Environment environment) { + String handler = environment.getProperty("_HANDLER"); + if (StringUtils.hasText(handler)) { + handler = handler.split(":")[0]; + logger.info("AWS Handler: " + handler); + try { + Thread.currentThread().getContextClassLoader().loadClass(handler); + } + catch (Exception e) { + logger.debug("Will execute Lambda in Custom Runtime"); + return true; + } + } + return false; + } +} diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootAwsProxyExceptionHandler.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootAwsProxyExceptionHandler.java new file mode 100644 index 000000000..127ef6684 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootAwsProxyExceptionHandler.java @@ -0,0 +1,27 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; +import com.amazonaws.serverless.proxy.ExceptionHandler; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import org.springframework.web.ErrorResponse; + +/** + * This ExceptionHandler implementation enhances the standard AwsProxyExceptionHandler + * by mapping additional details from org.springframework.web.ErrorResponse + * + * As of now this class is identical with SpringAwsProxyExceptionHandler. We may consider + * moving it to a common module to share it in the future. + */ +public class SpringBootAwsProxyExceptionHandler extends AwsProxyExceptionHandler + implements ExceptionHandler { + @Override + public AwsProxyResponse handle(Throwable ex) { + if (ex instanceof ErrorResponse) { + return new AwsProxyResponse(((ErrorResponse) ex).getStatusCode().value(), + HEADERS, getErrorJson(ex.getMessage())); + } else { + return super.handle(ex); + } + } + +} diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java similarity index 90% rename from aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java rename to aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java index ce6bad7b1..45bf28efe 100644 --- a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -12,10 +12,26 @@ */ package com.amazonaws.serverless.proxy.spring; +import java.util.concurrent.CountDownLatch; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; + 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.ExceptionHandler; +import com.amazonaws.serverless.proxy.InitializationWrapper; +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.AwsHttpServletRequest; +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; +import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; +import com.amazonaws.serverless.proxy.internal.servlet.AwsServletContext; +import com.amazonaws.serverless.proxy.internal.servlet.AwsServletRegistration; import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; @@ -23,22 +39,9 @@ import com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveServletEmbeddedServerFactory; import com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory; import com.amazonaws.services.lambda.runtime.Context; -import org.slf4j.Logger; -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 org.springframework.web.servlet.DispatcherServlet; -import javax.servlet.Servlet; -import javax.servlet.ServletRegistration; -import javax.servlet.http.HttpServletRequest; -import java.util.concurrent.CountDownLatch; +import jakarta.servlet.Servlet; +import jakarta.servlet.http.HttpServletRequest; /** * SpringBoot implementation of the `LambdaContainerHandler` abstract class. This class uses the `LambdaSpringApplicationInitializer` @@ -181,20 +184,26 @@ protected void handleRequest(HttpServletRequest containerRequest, AwsHttpServlet Timer.stop("SPRINGBOOT2_HANDLE_REQUEST"); } + SpringApplicationBuilder getSpringApplicationBuilder(Class... sources) { + return new SpringApplicationBuilder(sources); + } @Override public void initialize() throws ContainerInitializationException { Timer.start("SPRINGBOOT2_COLD_START"); - SpringApplicationBuilder builder = new SpringApplicationBuilder(getEmbeddedContainerClasses()) + SpringApplicationBuilder builder = getSpringApplicationBuilder(getEmbeddedContainerClasses()) .web(springWebApplicationType); // .REACTIVE, .SERVLET + if(springBootInitializer != null) { + builder.main(springBootInitializer); + } if (springProfiles != null) { builder.profiles(springProfiles); } applicationContext = builder.run(); if (springWebApplicationType == WebApplicationType.SERVLET) { - ((AnnotationConfigServletWebServerApplicationContext)applicationContext).setServletContext(getServletContext()); + ((ConfigurableWebApplicationContext)applicationContext).setServletContext(getServletContext()); AwsServletRegistration reg = (AwsServletRegistration)getServletContext().getServletRegistration(DISPATCHER_SERVLET_REGISTRATION_NAME); if (reg != null) { reg.setLoadOnStartup(1); diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java similarity index 92% rename from aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java rename to aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java index b9894834c..e7ad017f1 100644 --- a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java @@ -13,13 +13,12 @@ package com.amazonaws.serverless.proxy.spring; import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.ExceptionHandler; 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; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; public final class SpringBootProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< RequestType, @@ -81,4 +80,9 @@ public SpringBootLambdaContainerHandler buildAndI initializationWrapper.start(handler); return handler; } + + @Override + protected ExceptionHandler defaultExceptionHandler() { + return new SpringBootAwsProxyExceptionHandler(); + } } diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java new file mode 100644 index 000000000..765c4befe --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java @@ -0,0 +1,91 @@ +package com.amazonaws.serverless.proxy.spring; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.AsyncInitializationWrapper; +import com.amazonaws.serverless.proxy.InitializationTypeHelper; +import com.amazonaws.serverless.proxy.internal.InitializableLambdaContainerHandler; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import org.springframework.cloud.function.serverless.web.FunctionClassUtils; +import org.springframework.cloud.function.serverless.web.ServerlessMVC; + +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.servlet.http.HttpServletRequest; + +/* + * Copyright 2023 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. + */ + +/** + * An implementation of {@link RequestStreamHandler} which delegates to + * Spring Cloud Function serverless web module managed by Spring team. + * + * It requires no sub-classing from the user other then being identified as "Handler". + * The configuration class(es) should be provided via MAIN_CLASS environment variable. + * + */ +public class SpringDelegatingLambdaContainerHandler implements RequestStreamHandler { + private final ServerlessMVC mvc; + private final ObjectMapper mapper; + private final AwsProxyHttpServletResponseWriter responseWriter; + + public SpringDelegatingLambdaContainerHandler() throws ContainerInitializationException { + this(new Class[] {FunctionClassUtils.getStartClass()}); + } + + public SpringDelegatingLambdaContainerHandler(final Class... startupClasses) throws ContainerInitializationException { + SpringDelegatingInitHandler initHandler = new SpringDelegatingInitHandler(startupClasses); + if (InitializationTypeHelper.isAsyncInitializationDisabled()) { + initHandler.initialize(); + } else { + AsyncInitializationWrapper asyncInitWrapper = new AsyncInitializationWrapper(); + asyncInitWrapper.start(initHandler); + } + this.mvc = initHandler.getMvc(); + this.mapper = new ObjectMapper(); + this.responseWriter = new AwsProxyHttpServletResponseWriter(); + } + + @Override + public void handleRequest(InputStream input, OutputStream output, Context lambdaContext) throws IOException { + HttpServletRequest httpServletRequest = AwsSpringHttpProcessingUtils + .generateHttpServletRequest(input, lambdaContext, this.mvc.getServletContext(), this.mapper); + AwsProxyResponse awsProxyResponse = AwsSpringHttpProcessingUtils.processRequest(httpServletRequest, mvc, responseWriter); + this.mapper.writeValue(output, awsProxyResponse); + } + + private static final class SpringDelegatingInitHandler implements InitializableLambdaContainerHandler { + private ServerlessMVC mvc; + private final Class[] startupClasses; + + public SpringDelegatingInitHandler(final Class... startupClasses) { + this.startupClasses = startupClasses; + } + + @Override + public void initialize() throws ContainerInitializationException { + this.mvc = ServerlessMVC.INSTANCE(this.startupClasses); + this.mvc.waitForContext(); + } + + public ServerlessMVC getMvc() { + return this.mvc; + } + } +} diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java similarity index 99% rename from aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java rename to aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java index 9b2c04948..3376452e2 100644 --- a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java @@ -22,7 +22,7 @@ import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; -import javax.servlet.*; +import jakarta.servlet.*; import java.io.IOException; import java.util.Enumeration; diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java similarity index 92% rename from aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java rename to aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java index ad228f7d4..76ee919dc 100644 --- a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java @@ -21,12 +21,12 @@ import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.core.Ordered; -import javax.servlet.ServletException; +import jakarta.servlet.ServletException; @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) public class ServerlessServletEmbeddedServerFactory implements ServletWebServerFactory, WebServer { - private ServletContextInitializer[] initializers; - private AwsLambdaServletContainerHandler handler; + @SuppressWarnings("rawtypes") + private AwsLambdaServletContainerHandler handler; public ServerlessServletEmbeddedServerFactory() { super(); @@ -35,7 +35,6 @@ public ServerlessServletEmbeddedServerFactory() { @Override public WebServer getWebServer(ServletContextInitializer... initializers) { - this.initializers = initializers; for (ServletContextInitializer i : initializers) { try { if (handler.getServletContext() == null) { diff --git a/aws-serverless-java-container-springboot3/src/main/resources/META-INF/spring.factories b/aws-serverless-java-container-springboot3/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..cd5c2e70b --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.context.ApplicationContextInitializer=\ +com.amazonaws.serverless.proxy.spring.AwsSpringWebRuntimeInitializer diff --git a/aws-serverless-java-container-springboot3/src/main/resources/META-INF/spring/aot.factories b/aws-serverless-java-container-springboot3/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 000000000..44acc0d83 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1 @@ +org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=com.amazonaws.serverless.proxy.spring.AwsSpringAotTypesProcessor \ No newline at end of file diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/AWSWebRuntimeTests.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/AWSWebRuntimeTests.java new file mode 100644 index 000000000..9903e8f8b --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/AWSWebRuntimeTests.java @@ -0,0 +1,39 @@ +package com.amazonaws.serverless.proxy.spring; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; + +public class AWSWebRuntimeTests { + + @AfterEach + public void after() { + System.clearProperty("_HANDLER"); + } + + @Test + public void testWebRuntimeInitialization() throws Exception { + try (ConfigurableApplicationContext context = SpringApplication.run(EmptyApplication.class);) { + assertFalse(context.getBeansOfType(AwsSpringWebCustomRuntimeEventLoop.class).size() > 0); + } + System.setProperty("_HANDLER", "foo"); + AwsSpringWebCustomRuntimeEventLoop loop = null; + try (ConfigurableApplicationContext context = SpringApplication.run(EmptyApplication.class);) { + Thread.sleep(100); + assertTrue(context.getBeansOfType(AwsSpringWebCustomRuntimeEventLoop.class).size() > 0); + loop = context.getBean(AwsSpringWebCustomRuntimeEventLoop.class); + assertTrue(loop.isRunning()); + } + assertFalse(loop.isRunning()); + } + + @EnableAutoConfiguration + private static class EmptyApplication { + + } +} diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtilsTests.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtilsTests.java new file mode 100644 index 000000000..94232cbff --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtilsTests.java @@ -0,0 +1,290 @@ +package com.amazonaws.serverless.proxy.spring; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; + +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.model.AlbContext; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; +import org.springframework.cloud.function.serverless.web.ServerlessMVC; +import org.springframework.cloud.function.serverless.web.ServerlessServletContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.servlet.http.HttpServletRequest; + +import static org.junit.jupiter.api.Assertions.*; + +public class AwsSpringHttpProcessingUtilsTests { + + private static String API_GATEWAY_EVENT = "{\n" + + " \"version\": \"1.0\",\n" + + " \"resource\": \"$default\",\n" + + " \"path\": \"/async\",\n" + + " \"httpMethod\": \"POST\",\n" + + " \"headers\": {\n" + + " \"Content-Length\": \"45\",\n" + + " \"Content-Type\": \"application/json\",\n" + + " \"Host\": \"i76bfh111.execute-api.eu-west-3.amazonaws.com\",\n" + + " \"User-Agent\": \"curl/7.79.1\",\n" + + " \"X-Amzn-Trace-Id\": \"Root=1-64087690-2151375b219d3ba3389ea84e\",\n" + + " \"X-Forwarded-For\": \"109.210.252.44\",\n" + + " \"X-Forwarded-Port\": \"443\",\n" + + " \"X-Forwarded-Proto\": \"https\",\n" + + " \"accept\": \"*/*\"\n" + + " },\n" + + " \"multiValueHeaders\": {\n" + + " \"Content-Length\": [\n" + + " \"45\"\n" + + " ],\n" + + " \"Content-Type\": [\n" + + " \"application/json\"\n" + + " ],\n" + + " \"Host\": [\n" + + " \"i76bfhczs0.execute-api.eu-west-3.amazonaws.com\"\n" + + " ],\n" + + " \"User-Agent\": [\n" + + " \"curl/7.79.1\"\n" + + " ],\n" + + " \"X-Amzn-Trace-Id\": [\n" + + " \"Root=1-64087690-2151375b219d3ba3389ea84e\"\n" + + " ],\n" + + " \"X-Forwarded-For\": [\n" + + " \"109.210.252.44\"\n" + + " ],\n" + + " \"X-Forwarded-Port\": [\n" + + " \"443\"\n" + + " ],\n" + + " \"X-Forwarded-Proto\": [\n" + + " \"https\"\n" + + " ],\n" + + " \"accept\": [\n" + + " \"*/*\"\n" + + " ]\n" + + " },\n" + + " \"queryStringParameters\": {\n" + + " \"abc\": \"xyz\",\n" + + " \"parameter1\": \"value2\"\n" + + " },\n" + + " \"multiValueQueryStringParameters\": {\n" + + " \"abc\": [\n" + + " \"xyz\"\n" + + " ],\n" + + " \"parameter1\": [\n" + + " \"value1\",\n" + + " \"value2\"\n" + + " ]\n" + + " },\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"123456789098\",\n" + + " \"apiId\": \"i76bfhczs0\",\n" + + " \"domainName\": \"i76bfhc111.execute-api.eu-west-3.amazonaws.com\",\n" + + " \"domainPrefix\": \"i76bfhczs0\",\n" + + " \"extendedRequestId\": \"Bdd2ngt5iGYEMIg=\",\n" + + " \"httpMethod\": \"POST\",\n" + + " \"path\": \"/pets\",\n" + + " \"protocol\": \"HTTP/1.1\",\n" + + " \"requestId\": \"Bdd2ngt5iGYEMIg=\",\n" + + " \"requestTime\": \"08/Mar/2023:11:50:40 +0000\",\n" + + " \"requestTimeEpoch\": 1678276240455,\n" + + " \"resourceId\": \"$default\",\n" + + " \"resourcePath\": \"$default\",\n" + + " \"stage\": \"$default\"\n" + + " },\n" + + " \"pathParameters\": null,\n" + + " \"stageVariables\": null,\n" + + " \"body\": \"{\\\"name\\\":\\\"bob\\\"}\",\n" + + " \"isBase64Encoded\": false\n" + + "}"; + + private static String API_GATEWAY_EVENT_V2 = "{\n" + + " \"version\": \"2.0\",\n" + + " \"routeKey\": \"$default\",\n" + + " \"rawPath\": \"/async\",\n" + + " \"rawQueryString\": \"parameter1=value1¶meter1=value2¶meter2=value\",\n" + + " \"cookies\": [\n" + + " \"cookie1\",\n" + + " \"cookie2\"\n" + + " ],\n" + + " \"headers\": {\n" + + " \"header1\": \"value1\",\n" + + " \"header2\": \"value1,value2\",\n" + + " \"User-Agent\": \"curl/7.79.1\",\n" + + " \"X-Forwarded-Port\": \"443\"\n" + + " },\n" + + " \"queryStringParameters\": {\n" + + " \"parameter1\": \"value1,value2\",\n" + + " \"parameter2\": \"value\"\n" + + " },\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"123456789012\",\n" + + " \"apiId\": \"api-id\",\n" + + " \"authentication\": {\n" + + " \"clientCert\": {\n" + + " \"clientCertPem\": \"CERT_CONTENT\",\n" + + " \"subjectDN\": \"www.example.com\",\n" + + " \"issuerDN\": \"Example issuer\",\n" + + " \"serialNumber\": \"a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1\",\n" + + " \"validity\": {\n" + + " \"notBefore\": \"May 28 12:30:02 2019 GMT\",\n" + + " \"notAfter\": \"Aug 5 09:36:04 2021 GMT\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"authorizer\": {\n" + + " \"jwt\": {\n" + + " \"claims\": {\n" + + " \"claim1\": \"value1\",\n" + + " \"claim2\": \"value2\"\n" + + " },\n" + + " \"scopes\": [\n" + + " \"scope1\",\n" + + " \"scope2\"\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"domainName\": \"id.execute-api.us-east-1.amazonaws.com\",\n" + + " \"domainPrefix\": \"id\",\n" + + " \"http\": {\n" + + " \"method\": \"POST\",\n" + + " \"path\": \"/async\",\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" + + " \"pathParameters\": {\n" + + " \"parameter1\": \"value1\"\n" + + " },\n" + + " \"isBase64Encoded\": false,\n" + + " \"stageVariables\": {\n" + + " \"stageVariable1\": \"value1\",\n" + + " \"stageVariable2\": \"value2\"\n" + + " }\n" + + "}"; + + private static final String ALB_EVENT = "{\n" + + " \"requestContext\": {\n" + + " \"elb\": {\n" + + " \"targetGroupArn\": \"arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09\"\n" + + " }\n" + + " },\n" + + " \"httpMethod\": \"POST\",\n" + + " \"path\": \"/async\",\n" + + " \"multiValueQueryStringParameters\": { \"parameter1\": [\"value1\", \"value2\"], \"parameter2\": [\"1970-01-01T00%3A00%3A00.004Z\"]},\n" + + " \"multiValueHeaders\": {\n" + + " \"accept\": [\"text/html,application/xhtml+xml\"],\n" + + " \"accept-language\": [\"en-US,en;q=0.8\"],\n" + + " \"content-type\": [\"text/plain\"],\n" + + " \"cookie\": [\"cookies\"],\n" + + " \"host\": [\"lambda-846800462-us-east-2.elb.amazonaws.com\"],\n" + + " \"User-Agent\": [\"curl/7.79.1\"],\n" + + " \"x-amzn-trace-id\": [\"Root=1-5bdb40ca-556d8b0c50dc66f0511bf520\"],\n" + + " \"x-forwarded-for\": [\"72.21.198.66\"],\n" + + " \"x-forwarded-port\": [\"443\"],\n" + + " \"x-forwarded-proto\": [\"https\"]\n" + + " },\n" + + " \"isBase64Encoded\": false,\n" + + " \"body\": \"request_body\"\n" + + "}"; + + private final ObjectMapper mapper = new ObjectMapper(); + + public static Collection data() { + return Arrays.asList(new String[]{API_GATEWAY_EVENT, API_GATEWAY_EVENT_V2, ALB_EVENT}); + } + + @MethodSource("data") + @ParameterizedTest + public void validateHttpServletRequestGenerationWithInputStream(String jsonEvent) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonEvent.getBytes(StandardCharsets.UTF_8)); + ServerlessServletContext servletContext = new ServerlessServletContext(); + HttpServletRequest request = AwsSpringHttpProcessingUtils.generateHttpServletRequest(inputStream, null, servletContext, mapper); + assertRequest(request); + } + + private static void assertRequest(HttpServletRequest request) { + assertEquals("curl/7.79.1", request.getHeader("User-Agent")); + assertEquals("443", request.getHeader("X-Forwarded-Port")); + assertEquals("POST", request.getMethod()); + assertEquals("/async", request.getRequestURI()); + assertNotNull(request.getServletContext()); + // parameter handling for 2.0 requests is currently not spec compliant and to be fixed in future version + // see also GitHub issue https://github.com/aws/serverless-java-container/issues/1278 + if (!(request.getAttribute(RequestReader.HTTP_API_EVENT_PROPERTY) instanceof HttpApiV2ProxyRequest)) { + assertEquals("value1", request.getParameter("parameter1")); + assertArrayEquals(new String[]{"value1", "value2"}, request.getParameterValues("parameter1")); + } + if (request.getAttribute(RequestReader.ALB_CONTEXT_PROPERTY) instanceof AlbContext) { + // query params should be decoded + assertEquals("1970-01-01T00:00:00.004Z", request.getParameter("parameter2")); + } + } + + @MethodSource("data") + @ParameterizedTest + public void validateHttpServletRequestGenerationWithJson(String jsonEvent) { + ServerlessServletContext servletContext = new ServerlessServletContext(); + HttpServletRequest request = AwsSpringHttpProcessingUtils.generateHttpServletRequest(jsonEvent, null, servletContext, mapper); + // spot check some headers + assertRequest(request); + } + + @MethodSource("data") + @ParameterizedTest + public void validateRequestResponse(String jsonEvent) throws Exception { + try (ConfigurableApplicationContext context = SpringApplication.run(EmptyApplication.class);) { + ServerlessMVC mvc = ServerlessMVC.INSTANCE((ServletWebServerApplicationContext) context); + AwsProxyHttpServletResponseWriter responseWriter = new AwsProxyHttpServletResponseWriter(); + AwsProxyResponse awsResponse = AwsSpringHttpProcessingUtils.processRequest( + AwsSpringHttpProcessingUtils.generateHttpServletRequest(jsonEvent, null, + mvc.getServletContext(), mapper), mvc, responseWriter); + assertEquals("hello", awsResponse.getBody()); + assertEquals(200, awsResponse.getStatusCode()); + } + + } + + @EnableAutoConfiguration + @Configuration + public static class EmptyApplication { + @RestController + @EnableWebMvc + public static class MyController { + @PostMapping(path = "/async") + public String async(@RequestBody String body) { + return "hello"; + } + } + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf((csrf) -> csrf.disable()); + return http.build(); + } + } +} diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/JpaAppTest.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/JpaAppTest.java new file mode 100644 index 000000000..a111e510a --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/JpaAppTest.java @@ -0,0 +1,52 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.jpaapp.LambdaHandler; +import com.amazonaws.serverless.proxy.spring.jpaapp.MessageController; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class JpaAppTest { + + LambdaHandler handler; + MockLambdaContext lambdaContext = new MockLambdaContext(); + + private String type; + + public static Collection data() { + return Arrays.asList(new Object[]{"API_GW", "ALB", "HTTP_API"}); + } + + public void initJpaAppTest(String reqType) { + type = reqType; + handler = new LambdaHandler(type); + } + + @MethodSource("data") + @ParameterizedTest + void asyncRequest(String reqType) { + initJpaAppTest(reqType); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/async", "POST") + .json() + .body("{\"name\":\"kong\"}"); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertEquals("{\"name\":\"KONG\"}", resp.getBody()); + } + + @MethodSource("data") + @ParameterizedTest + void helloRequest_respondsWithSingleMessage(String reqType) { + initJpaAppTest(reqType); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/hello", "GET"); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + } + +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SecurityAppTest.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SecurityAppTest.java similarity index 82% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SecurityAppTest.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SecurityAppTest.java index e9682d4c4..d0b579509 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SecurityAppTest.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SecurityAppTest.java @@ -6,13 +6,13 @@ import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.spring.securityapp.LambdaHandler; import com.amazonaws.serverless.proxy.spring.securityapp.SecurityConfig; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class SecurityAppTest { @@ -24,7 +24,7 @@ public SecurityAppTest() { } @Test - public void helloRequest_withAuth_respondsWithSingleMessage() { + void helloRequest_withAuth_respondsWithSingleMessage() { AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); assertEquals(401, resp.getStatusCode()); diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java similarity index 73% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java index 7c80dc86b..b87c80ce6 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java @@ -3,18 +3,15 @@ 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.model.ContainerConfig; import com.amazonaws.serverless.proxy.spring.servletapp.*; 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 org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -23,9 +20,8 @@ import java.util.Collection; import java.util.stream.Collectors; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -@RunWith(Parameterized.class) public class ServletAppTest { LambdaHandler handler; @@ -33,25 +29,39 @@ public class ServletAppTest { private String type; - @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); + return Arrays.asList(new Object[]{"API_GW", "ALB", "HTTP_API"}); } - public ServletAppTest(String reqType) { + public void initServletAppTest(String reqType) { type = reqType; handler = new LambdaHandler(type); } - @Test - public void helloRequest_respondsWithSingleMessage() { + @MethodSource("data") + @ParameterizedTest + void asyncRequest(String reqType) { + initServletAppTest(reqType); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/async", "POST") + .json() + .body("{\"name\":\"bob\"}"); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertEquals("{\"name\":\"BOB\"}", resp.getBody()); + } + + @MethodSource("data") + @ParameterizedTest + void helloRequest_respondsWithSingleMessage(String reqType) { + initServletAppTest(reqType); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/hello", "GET"); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); - Assert.assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); } - @Test - public void validateRequest_invalidData_respondsWith400() { + @MethodSource("data") + @ParameterizedTest + void validateRequest_invalidData_respondsWith400(String reqType) { + initServletAppTest(reqType); UserData ud = new UserData(); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/validate", "POST") .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN) @@ -79,8 +89,10 @@ public void validateRequest_invalidData_respondsWith400() { assertEquals(400, resp.getStatusCode()); } - @Test - public void messageObject_parsesObject_returnsCorrectMessage() { + @MethodSource("data") + @ParameterizedTest + void messageObject_parsesObject_returnsCorrectMessage(String reqType) { + initServletAppTest(reqType); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/message", "POST") .json() .body(new MessageData("test message")); @@ -90,8 +102,10 @@ public void messageObject_parsesObject_returnsCorrectMessage() { assertEquals("test message", resp.getBody()); } - @Test - public void messageObject_propertiesInContentType_returnsCorrectMessage() { + @MethodSource("data") + @ParameterizedTest + void messageObject_propertiesInContentType_returnsCorrectMessage(String reqType) { + initServletAppTest(reqType); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/message", "POST") .header(HttpHeaders.CONTENT_TYPE, "application/json;v=1") .header(HttpHeaders.ACCEPT, "application/json;v=1") @@ -102,8 +116,10 @@ public void messageObject_propertiesInContentType_returnsCorrectMessage() { assertEquals("test message", resp.getBody()); } - @Test - public void echoMessage_fileNameLikeParameter_returnsMessage() { + @MethodSource("data") + @ParameterizedTest + void echoMessage_fileNameLikeParameter_returnsMessage(String reqType) { + initServletAppTest(reqType); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/echo/test.test.test", "GET"); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); assertNotNull(resp); @@ -111,8 +127,10 @@ public void echoMessage_fileNameLikeParameter_returnsMessage() { assertEquals("test.test.test", resp.getBody()); } - @Test - public void getUtf8String_returnsValidUtf8String() { + @MethodSource("data") + @ParameterizedTest + void getUtf8String_returnsValidUtf8String(String reqType) { + initServletAppTest(reqType); // We expect strings to come back as UTF-8 correctly because Spring itself will call the setCharacterEncoding // method on the response to set it to UTF- LambdaContainerHandler.getContainerConfig().setDefaultContentCharset(ContainerConfig.DEFAULT_CONTENT_CHARSET); @@ -125,18 +143,22 @@ public void getUtf8String_returnsValidUtf8String() { assertEquals(MessageController.UTF8_RESPONSE, resp.getBody()); } - @Test - public void getUtf8Json_returnsValidUtf8String() { + @MethodSource("data") + @ParameterizedTest + void getUtf8Json_returnsValidUtf8String(String reqType) { + initServletAppTest(reqType); LambdaContainerHandler.getContainerConfig().setDefaultContentCharset(ContainerConfig.DEFAULT_CONTENT_CHARSET); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/content-type/jsonutf8", "GET"); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); assertNotNull(resp); assertEquals(200, resp.getStatusCode()); - assertEquals("{\"s\":\""+MessageController.UTF8_RESPONSE+"\"}", resp.getBody()); + assertEquals("{\"s\":\"" + MessageController.UTF8_RESPONSE + "\"}", resp.getBody()); } - @Test - public void stream_getUtf8String_returnsValidUtf8String() throws IOException { + @MethodSource("data") + @ParameterizedTest + void stream_getUtf8String_returnsValidUtf8String(String reqType) throws IOException { + initServletAppTest(reqType); LambdaContainerHandler.getContainerConfig().setDefaultContentCharset(ContainerConfig.DEFAULT_CONTENT_CHARSET); LambdaStreamHandler streamHandler = new LambdaStreamHandler(type); AwsProxyRequestBuilder reqBuilder = new AwsProxyRequestBuilder("/content-type/utf8", "GET") @@ -160,8 +182,10 @@ public void stream_getUtf8String_returnsValidUtf8String() throws IOException { assertEquals(MessageController.UTF8_RESPONSE, resp.getBody()); } - @Test - public void stream_getUtf8Json_returnsValidUtf8String() throws IOException { + @MethodSource("data") + @ParameterizedTest + void stream_getUtf8Json_returnsValidUtf8String(String reqType) throws IOException { + initServletAppTest(reqType); LambdaContainerHandler.getContainerConfig().setDefaultContentCharset(ContainerConfig.DEFAULT_CONTENT_CHARSET); LambdaStreamHandler streamHandler = new LambdaStreamHandler(type); AwsProxyRequestBuilder reqBuilder = new AwsProxyRequestBuilder("/content-type/jsonutf8", "GET"); @@ -181,19 +205,23 @@ public void stream_getUtf8Json_returnsValidUtf8String() throws IOException { AwsProxyResponse resp = LambdaContainerHandler.getObjectMapper().readValue(out.toByteArray(), AwsProxyResponse.class); assertNotNull(resp); assertEquals(200, resp.getStatusCode()); - assertEquals("{\"s\":\""+MessageController.UTF8_RESPONSE+"\"}", resp.getBody()); + assertEquals("{\"s\":\"" + MessageController.UTF8_RESPONSE + "\"}", resp.getBody()); } - @Test - public void springExceptionMapping_throw404Ex_expectMappedTo404() { + @MethodSource("data") + @ParameterizedTest + void springExceptionMapping_throw404Ex_expectMappedTo404(String reqType) { + initServletAppTest(reqType); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/ex/customstatus", "GET"); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); assertNotNull(resp); assertEquals(404, resp.getStatusCode()); } - @Test - public void echoMessage_populatesSingleValueHeadersForHttpApiV2() { + @MethodSource("data") + @ParameterizedTest + void echoMessage_populatesSingleValueHeadersForHttpApiV2(String reqType) { + initServletAppTest(reqType); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/message", "POST") .header(HttpHeaders.CONTENT_TYPE, "application/json;v=1") .header(HttpHeaders.ACCEPT, "application/json;v=1") diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java similarity index 81% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java index 8dabc5f66..f5e83e85e 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java @@ -7,18 +7,17 @@ import com.amazonaws.serverless.proxy.spring.slowapp.LambdaHandler; import com.amazonaws.serverless.proxy.spring.slowapp.MessageController; import com.amazonaws.serverless.proxy.spring.slowapp.SlowTestApplication; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.time.Instant; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class SlowAppTest { @Test - public void slowAppInit_continuesInBackgroundThread_returnsCorrect() { + void slowAppInit_continuesInBackgroundThread_returnsCorrect() { LambdaHandler slowApp = new LambdaHandler(); System.out.println("Start time: " + slowApp.getConstructorTime()); assertTrue(slowApp.getConstructorTime() < 10_000); @@ -28,6 +27,6 @@ public void slowAppInit_continuesInBackgroundThread_returnsCorrect() { long endRequestTime = Instant.now().toEpochMilli(); assertTrue(endRequestTime - startRequestTime > SlowTestApplication.SlowDownInit.INIT_SLEEP_TIME_MS - 10_000); assertEquals(200, resp.getStatusCode()); - Assert.assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); } } diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandlerTest.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandlerTest.java new file mode 100644 index 000000000..871a751bb --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandlerTest.java @@ -0,0 +1,76 @@ +package com.amazonaws.serverless.proxy.spring; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +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.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.servletapp.ServletApplication; +import com.amazonaws.serverless.proxy.spring.webfluxapp.WebFluxTestApplication; +import java.util.Collection; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.builder.SpringApplicationBuilder; + +class SpringBootLambdaContainerHandlerTest { + + SpringBootLambdaContainerHandler handler; + SpringApplicationBuilder springApplicationBuilder; + + public static Collection data() { + return List.of(new TestData(WebApplicationType.SERVLET, ServletApplication.class), + new TestData(WebApplicationType.REACTIVE, WebFluxTestApplication.class)); + } + + private void initSpringBootLambdaContainerHandlerTest(Class springBootInitializer, + WebApplicationType applicationType) { + handler = Mockito.spy(new SpringBootLambdaContainerHandler<>(AwsProxyRequest.class, + AwsProxyResponse.class, + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + springBootInitializer, + new InitializationWrapper(), + applicationType)); + + doAnswer(d -> { + springApplicationBuilder = ((SpringApplicationBuilder) Mockito.spy(d.callRealMethod())); + return springApplicationBuilder; + }).when(handler).getSpringApplicationBuilder(any(Class[].class)); + } + + @ParameterizedTest + @MethodSource("data") + void initialize_withSpringBootInitializer(TestData data) throws ContainerInitializationException { + initSpringBootLambdaContainerHandlerTest(data.springBootApplication(), data.applicationType()); + handler.initialize(); + + verify(springApplicationBuilder, times(1)).main(data.springBootApplication()); + } + + @ParameterizedTest + @EnumSource(WebApplicationType.class) + void initialize_withoutSpringBootInitializer(WebApplicationType webApplicationType) { + initSpringBootLambdaContainerHandlerTest(null, webApplicationType); + assertThrows(IllegalArgumentException.class, handler::initialize, "Source must not be null"); + + verify(springApplicationBuilder, never()).main(any()); + } + + record TestData(WebApplicationType applicationType, Class springBootApplication) {} +} \ No newline at end of file diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java new file mode 100644 index 000000000..02ef21d9e --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java @@ -0,0 +1,350 @@ +package com.amazonaws.serverless.proxy.spring; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.util.CollectionUtils; + +import com.amazonaws.serverless.proxy.spring.servletapp.MessageData; +import com.amazonaws.serverless.proxy.spring.servletapp.ServletApplication; +import com.amazonaws.serverless.proxy.spring.servletapp.UserData; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.ws.rs.core.HttpHeaders; + +@SuppressWarnings("rawtypes") +public class SpringDelegatingLambdaContainerHandlerTests { + + private static String API_GATEWAY_EVENT = "{\n" + + " \"version\": \"1.0\",\n" + + " \"resource\": \"$default\",\n" + + " \"path\": \"/async\",\n" + + " \"httpMethod\": \"POST\",\n" + + " \"headers\": {\n" + + " \"Content-Length\": \"45\",\n" + + " \"Content-Type\": \"application/json\",\n" + + " \"Host\": \"i76bfh111.execute-api.eu-west-3.amazonaws.com\",\n" + + " \"User-Agent\": \"curl/7.79.1\",\n" + + " \"X-Amzn-Trace-Id\": \"Root=1-64087690-2151375b219d3ba3389ea84e\",\n" + + " \"X-Forwarded-For\": \"109.210.252.44\",\n" + + " \"X-Forwarded-Port\": \"443\",\n" + + " \"X-Forwarded-Proto\": \"https\",\n" + + " \"accept\": \"*/*\"\n" + + " },\n" + + " \"multiValueHeaders\": {\n" + + " \"Content-Length\": [\n" + + " \"45\"\n" + + " ],\n" + + " \"Content-Type\": [\n" + + " \"application/json\"\n" + + " ],\n" + + " \"Host\": [\n" + + " \"i76bfhczs0.execute-api.eu-west-3.amazonaws.com\"\n" + + " ],\n" + + " \"User-Agent\": [\n" + + " \"curl/7.79.1\"\n" + + " ],\n" + + " \"X-Amzn-Trace-Id\": [\n" + + " \"Root=1-64087690-2151375b219d3ba3389ea84e\"\n" + + " ],\n" + + " \"X-Forwarded-For\": [\n" + + " \"109.210.252.44\"\n" + + " ],\n" + + " \"X-Forwarded-Port\": [\n" + + " \"443\"\n" + + " ],\n" + + " \"X-Forwarded-Proto\": [\n" + + " \"https\"\n" + + " ],\n" + + " \"accept\": [\n" + + " \"*/*\"\n" + + " ]\n" + + " },\n" + + " \"queryStringParameters\": {\n" + + " \"abc\": \"xyz\",\n" + + " \"name\": \"Ricky\",\n" + + " \"foo\": \"baz\"\n" + + " },\n" + + " \"multiValueQueryStringParameters\": {\n" + + " \"abc\": [\n" + + " \"xyz\"\n" + + " ],\n" + + " \"name\": [\n" + + " \"Ricky\"\n" + + " ],\n" + + " \"foo\": [\n" + + " \"bar\",\n" + + " \"baz\"\n" + + " ]\n" + + " },\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"123456789098\",\n" + + " \"apiId\": \"i76bfhczs0\",\n" + + " \"domainName\": \"i76bfhc111.execute-api.eu-west-3.amazonaws.com\",\n" + + " \"domainPrefix\": \"i76bfhczs0\",\n" + + " \"extendedRequestId\": \"Bdd2ngt5iGYEMIg=\",\n" + + " \"httpMethod\": \"POST\",\n" + + " \"identity\": {\n" + + " \"accessKey\": null,\n" + + " \"accountId\": null,\n" + + " \"caller\": null,\n" + + " \"cognitoAmr\": null,\n" + + " \"cognitoAuthenticationProvider\": null,\n" + + " \"cognitoAuthenticationType\": null,\n" + + " \"cognitoIdentityId\": null,\n" + + " \"cognitoIdentityPoolId\": null,\n" + + " \"principalOrgId\": null,\n" + + " \"sourceIp\": \"109.210.252.44\",\n" + + " \"user\": null,\n" + + " \"userAgent\": \"curl/7.79.1\",\n" + + " \"userArn\": null\n" + + " },\n" + + " \"path\": \"/pets\",\n" + + " \"protocol\": \"HTTP/1.1\",\n" + + " \"requestId\": \"Bdd2ngt5iGYEMIg=\",\n" + + " \"requestTime\": \"08/Mar/2023:11:50:40 +0000\",\n" + + " \"requestTimeEpoch\": 1678276240455,\n" + + " \"resourceId\": \"$default\",\n" + + " \"resourcePath\": \"$default\",\n" + + " \"stage\": \"$default\"\n" + + " },\n" + + " \"pathParameters\": null,\n" + + " \"stageVariables\": null,\n" + + " \"body\": \"{\\\"name\\\":\\\"bob\\\"}\",\n" + + " \"isBase64Encoded\": false\n" + + "}"; + + private static String API_GATEWAY_EVENT_V2 = "{\n" + + " \"version\": \"2.0\",\n" + + " \"routeKey\": \"$default\",\n" + + " \"rawPath\": \"/my/path\",\n" + + " \"rawQueryString\": \"parameter1=value1¶meter1=value2&name=Ricky¶meter2=value\",\n" + + " \"cookies\": [\n" + + " \"cookie1\",\n" + + " \"cookie2\"\n" + + " ],\n" + + " \"headers\": {\n" + + " \"header1\": \"value1\",\n" + + " \"header2\": \"value1,value2\"\n" + + " },\n" + + " \"queryStringParameters\": {\n" + + " \"parameter1\": \"value1,value2\",\n" + + " \"name\": \"Ricky\",\n" + + " \"parameter2\": \"value\"\n" + + " },\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"123456789012\",\n" + + " \"apiId\": \"api-id\",\n" + + " \"authentication\": {\n" + + " \"clientCert\": {\n" + + " \"clientCertPem\": \"CERT_CONTENT\",\n" + + " \"subjectDN\": \"www.example.com\",\n" + + " \"issuerDN\": \"Example issuer\",\n" + + " \"serialNumber\": \"a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1\",\n" + + " \"validity\": {\n" + + " \"notBefore\": \"May 28 12:30:02 2019 GMT\",\n" + + " \"notAfter\": \"Aug 5 09:36:04 2021 GMT\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"authorizer\": {\n" + + " \"jwt\": {\n" + + " \"claims\": {\n" + + " \"claim1\": \"value1\",\n" + + " \"claim2\": \"value2\"\n" + + " },\n" + + " \"scopes\": [\n" + + " \"scope1\",\n" + + " \"scope2\"\n" + + " ]\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" + + " \"pathParameters\": {\n" + + " \"parameter1\": \"value1\"\n" + + " },\n" + + " \"isBase64Encoded\": false,\n" + + " \"stageVariables\": {\n" + + " \"stageVariable1\": \"value1\",\n" + + " \"stageVariable2\": \"value2\"\n" + + " }\n" + + "}"; + + private SpringDelegatingLambdaContainerHandler handler; + + private ObjectMapper mapper = new ObjectMapper(); + + public void initServletAppTest() throws ContainerInitializationException { + this.handler = new SpringDelegatingLambdaContainerHandler(ServletApplication.class); + } + + public static Collection data() { + return Arrays.asList(new String[]{API_GATEWAY_EVENT, API_GATEWAY_EVENT_V2}); + } + + @MethodSource("data") + @ParameterizedTest + public void validateComplesrequest(String jsonEvent) throws Exception { + initServletAppTest(); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", + "/foo/male/list/24", "{\"name\":\"bob\"}", false,null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(200, result.get("statusCode")); + String[] responseBody = ((String) result.get("body")).split("/"); + assertEquals("male", responseBody[0]); + assertEquals("24", responseBody[1]); + assertEquals("Ricky", responseBody[2]); + } + + @MethodSource("data") + @ParameterizedTest + public void testAsyncPost(String jsonEvent) throws Exception { + initServletAppTest(); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/async", "{\"name\":\"bob\"}",false, null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(200, result.get("statusCode")); + assertEquals("{\"name\":\"BOB\"}", result.get("body")); + } + + @MethodSource("data") + @ParameterizedTest + public void testValidate400(String jsonEvent) throws Exception { + initServletAppTest(); + UserData ud = new UserData(); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/validate", mapper.writeValueAsString(ud),false, null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(400, result.get("statusCode")); + assertEquals("3", result.get("body")); + } + + @MethodSource("data") + @ParameterizedTest + public void testValidate200(String jsonEvent) throws Exception { + initServletAppTest(); + UserData ud = new UserData(); + ud.setFirstName("bob"); + ud.setLastName("smith"); + ud.setEmail("foo@bar.com"); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/validate", mapper.writeValueAsString(ud),false, null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(200, result.get("statusCode")); + assertEquals("VALID", result.get("body")); + } + + @MethodSource("data") + @ParameterizedTest + public void testValidate200Base64(String jsonEvent) throws Exception { + initServletAppTest(); + UserData ud = new UserData(); + ud.setFirstName("bob"); + ud.setLastName("smith"); + ud.setEmail("foo@bar.com"); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/validate", + Base64.getMimeEncoder().encodeToString(mapper.writeValueAsString(ud).getBytes()),true, null)); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(200, result.get("statusCode")); + assertEquals("VALID", result.get("body")); + } + + + @MethodSource("data") + @ParameterizedTest + public void messageObject_parsesObject_returnsCorrectMessage(String jsonEvent) throws Exception { + initServletAppTest(); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/message", + mapper.writeValueAsString(new MessageData("test message")),false, null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(200, result.get("statusCode")); + assertEquals("test message", result.get("body")); + } + + + + @SuppressWarnings({"unchecked" }) + @MethodSource("data") + @ParameterizedTest + void messageObject_propertiesInContentType_returnsCorrectMessage(String jsonEvent) throws Exception { + initServletAppTest(); + + Map headers = new HashMap<>(); + headers.put(HttpHeaders.CONTENT_TYPE, "application/json;v=1"); + headers.put(HttpHeaders.ACCEPT, "application/json;v=1"); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/message", + mapper.writeValueAsString(new MessageData("test message")),false, headers)); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals("test message", result.get("body")); + } + + private byte[] generateHttpRequest(String jsonEvent, String method, String path, String body,boolean isBase64Encoded, Map headers) throws Exception { + Map requestMap = mapper.readValue(jsonEvent, Map.class); + if (requestMap.get("version").equals("2.0")) { + return generateHttpRequest2(requestMap, method, path, body, isBase64Encoded,headers); + } + return generateHttpRequest(requestMap, method, path, body,isBase64Encoded, headers); + } + + @SuppressWarnings({ "unchecked"}) + private byte[] generateHttpRequest(Map requestMap, String method, String path, String body,boolean isBase64Encoded, Map headers) throws Exception { + requestMap.put("path", path); + requestMap.put("httpMethod", method); + requestMap.put("body", body); + requestMap.put("isBase64Encoded", isBase64Encoded); + if (!CollectionUtils.isEmpty(headers)) { + requestMap.put("headers", headers); + } + return mapper.writeValueAsBytes(requestMap); + } + + @SuppressWarnings({ "unchecked"}) + private byte[] generateHttpRequest2(Map requestMap, String method, String path, String body,boolean isBase64Encoded, Map headers) throws Exception { + Map map = mapper.readValue(API_GATEWAY_EVENT_V2, Map.class); + Map http = (Map) ((Map) map.get("requestContext")).get("http"); + http.put("path", path); + http.put("method", method); + map.put("body", body); + map.put("isBase64Encoded", isBase64Encoded); + if (!CollectionUtils.isEmpty(headers)) { + map.put("headers", headers); + } + return mapper.writeValueAsBytes(map); + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java similarity index 65% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java index 811a1ab55..9c39fd905 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java @@ -1,26 +1,22 @@ package com.amazonaws.serverless.proxy.spring; -import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + 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; -import org.junit.runners.Parameterized; - -import java.util.Arrays; -import java.util.Collection; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -@RunWith(Parameterized.class) public class WebFluxAppTest { LambdaHandler handler; @@ -28,34 +24,39 @@ public class WebFluxAppTest { private String type; - @Parameterized.Parameters public static Collection data() { - return Arrays.asList(new Object[] { "API_GW", "ALB", "HTTP_API" }); + return Arrays.asList(new Object[]{"API_GW", "ALB", "HTTP_API"}); } - public WebFluxAppTest(String reqType) { + public void initWebFluxAppTest(String reqType) { type = reqType; handler = new LambdaHandler(type); } - @Test - public void helloRequest_respondsWithSingleMessage() { + @MethodSource("data") + @ParameterizedTest + void helloRequest_respondsWithSingleMessage(String reqType) { + initWebFluxAppTest(reqType); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/single", "GET"); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); System.out.println(resp.getBody()); - Assert.assertEquals(MessageController.MESSAGE, resp.getBody()); + assertEquals(MessageController.MESSAGE, resp.getBody()); } - @Test - public void helloDoubleRequest_respondsWithDoubleMessage() { + @MethodSource("data") + @ParameterizedTest + void helloDoubleRequest_respondsWithDoubleMessage(String reqType) { + initWebFluxAppTest(reqType); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/double", "GET"); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); assertEquals(MessageController.MESSAGE + MessageController.MESSAGE, resp.getBody()); } - @Test - public void messageObject_parsesObject_returnsCorrectMessage() throws JsonProcessingException { + @MethodSource("data") + @ParameterizedTest + void messageObject_parsesObject_returnsCorrectMessage(String reqType) throws JsonProcessingException { + initWebFluxAppTest(reqType); AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/message", "POST") .json() .body(new MessageData("test message")); diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java similarity index 90% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java index 11779420c..5ffd4a311 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java @@ -9,14 +9,14 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.boot.WebApplicationType; import org.springframework.boot.web.servlet.ServletContextInitializer; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.fail; public class ServerlessServletEmbeddedServerFactoryTest { private SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler<>( @@ -35,7 +35,7 @@ public ServerlessServletEmbeddedServerFactoryTest() throws ContainerInitializati } @Test - public void getWebServer_callsInitializers() { + void getWebServer_callsInitializers() { ServerlessServletEmbeddedServerFactory factory = new ServerlessServletEmbeddedServerFactory(); factory.getWebServer(new ServletContextInitializer() { @Override diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/DatabaseConfig.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/DatabaseConfig.java new file mode 100644 index 000000000..aeef7c65e --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/DatabaseConfig.java @@ -0,0 +1,23 @@ +package com.amazonaws.serverless.proxy.spring.jpaapp; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.DriverManagerDataSource; + +import javax.sql.DataSource; + +@Configuration +public class DatabaseConfig { + + @Bean + public DataSource dataSource() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName("org.h2.Driver"); + dataSource.setUrl("jdbc:h2:mem:testdb"); + dataSource.setUsername("sa"); + dataSource.setPassword(""); + + return dataSource; + } +} + diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/JpaApplication.java similarity index 67% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/JpaApplication.java index 07ddbab43..5aced5e28 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/JpaApplication.java @@ -1,8 +1,10 @@ -package com.amazonaws.serverless.proxy.spring.servletapp; +package com.amazonaws.serverless.proxy.spring.jpaapp; +import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; -import org.springframework.context.annotation.ComponentScan; +import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggingSystem; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; @SpringBootApplication(exclude = { @@ -12,5 +14,4 @@ org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class }) @Import(MessageController.class) -public class ServletApplication { -} +public class JpaApplication {} diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/LambdaHandler.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/LambdaHandler.java new file mode 100644 index 000000000..0cf67c10f --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/LambdaHandler.java @@ -0,0 +1,59 @@ +package com.amazonaws.serverless.proxy.spring.jpaapp; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +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 { + private static SpringBootLambdaContainerHandler handler; + private static SpringBootLambdaContainerHandler httpApiHandler; + private String type; + + public LambdaHandler(String reqType) { + type = reqType; + try { + switch (type) { + case "API_GW": + case "ALB": + handler = new SpringBootProxyHandlerBuilder() + .defaultProxy() + .initializationWrapper(new InitializationWrapper()) + .servletApplication() + .springBootApplication(JpaApplication.class) + .buildAndInitialize(); + break; + case "HTTP_API": + httpApiHandler = new SpringBootProxyHandlerBuilder() + .defaultHttpApiV2Proxy() + .initializationWrapper(new InitializationWrapper()) + .servletApplication() + .springBootApplication(JpaApplication.class) + .buildAndInitialize(); + break; + } + } catch (ContainerInitializationException e) { + e.printStackTrace(); + } + } + + @Override + 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-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/MessageController.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/MessageController.java new file mode 100644 index 000000000..a85292262 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/MessageController.java @@ -0,0 +1,31 @@ +package com.amazonaws.serverless.proxy.spring.jpaapp; + +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.async.DeferredResult; +import java.util.Collections; +import java.util.Map; + +@RestController +public class MessageController { + + public static final String HELLO_MESSAGE = "Hello"; + + @RequestMapping(path="/hello", method=RequestMethod.GET, produces = {"text/plain"}) + public String hello() { + return HELLO_MESSAGE; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @RequestMapping(path = "/async", method = RequestMethod.POST) + @ResponseBody + public DeferredResult> asyncResult(@RequestBody Map value) { + DeferredResult result = new DeferredResult<>(); + result.setResult(Collections.singletonMap("name", value.get("name").toUpperCase())); + return result; + } + +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/LambdaHandler.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/LambdaHandler.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/LambdaHandler.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/LambdaHandler.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/MessageController.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/MessageController.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/MessageController.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/MessageController.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityApplication.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityApplication.java similarity index 69% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityApplication.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityApplication.java index cafcd4000..d4036dcfe 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityApplication.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityApplication.java @@ -6,7 +6,10 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.web.reactive.config.EnableWebFlux; -@SpringBootApplication +@SpringBootApplication(exclude = { + org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class, + org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.class +}) @EnableWebFluxSecurity @EnableWebFlux @Import(SecurityConfig.class) diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityConfig.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityConfig.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityConfig.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityConfig.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaStreamHandler.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaStreamHandler.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaStreamHandler.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaStreamHandler.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java similarity index 82% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java index 6bd516324..1923396c6 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java @@ -5,9 +5,12 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.server.ResponseStatusException; -import javax.validation.Valid; +import jakarta.validation.Valid; + +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -18,6 +21,15 @@ public class MessageController { public static final String UTF8_RESPONSE = "öüäß фрыцшщ"; public static final String EX_MESSAGE = "404 exception message"; + @SuppressWarnings({ "unchecked", "rawtypes" }) + @RequestMapping(path = "/async", method = RequestMethod.POST) + @ResponseBody + public DeferredResult> asyncResult(@RequestBody Map value) { + DeferredResult result = new DeferredResult<>(); + result.setResult(Collections.singletonMap("name", value.get("name").toUpperCase())); + return result; + } + @RequestMapping(path="/hello", method=RequestMethod.GET, produces = {"text/plain"}) public String hello() { return HELLO_MESSAGE; diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageData.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageData.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageData.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageData.java diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java new file mode 100644 index 000000000..9f01859aa --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java @@ -0,0 +1,32 @@ +package com.amazonaws.serverless.proxy.spring.servletapp; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@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, + org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class, + org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.class +}) +@Import(MessageController.class) +@RestController +public class ServletApplication { + + @RequestMapping(path = "/foo/{gender}/list/{age}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public String complexRequest( + @PathVariable("gender") String gender, + @PathVariable("age") String age, + @RequestParam("name") String name + ) { + return gender + "/" + age + "/" + name; + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/UserData.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/UserData.java similarity index 85% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/UserData.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/UserData.java index e180c1738..379291a39 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/UserData.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/UserData.java @@ -1,9 +1,9 @@ package com.amazonaws.serverless.proxy.spring.servletapp; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; public class UserData { @NotBlank diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/LambdaHandler.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/LambdaHandler.java similarity index 98% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/LambdaHandler.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/LambdaHandler.java index ec6993a7d..22f75e7a9 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/LambdaHandler.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/LambdaHandler.java @@ -21,7 +21,6 @@ public LambdaHandler() { System.out.println("startCall: " + startTime); handler = new SpringBootProxyHandlerBuilder() .defaultProxy() - .asyncInit() .springBootApplication(SlowTestApplication.class) .buildAndInitialize(); constructorTime = Instant.now().toEpochMilli() - startTime; diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/MessageController.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/MessageController.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/MessageController.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/MessageController.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/SlowTestApplication.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/SlowTestApplication.java similarity index 78% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/SlowTestApplication.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/SlowTestApplication.java index b3fe177a1..006e51e45 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/SlowTestApplication.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/SlowTestApplication.java @@ -8,7 +8,9 @@ @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.jdbc.DataSourceAutoConfiguration.class, + org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.class }) public class SlowTestApplication { diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/LambdaHandler.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/LambdaHandler.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/LambdaHandler.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/LambdaHandler.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageData.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageData.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageData.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageData.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java similarity index 83% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java rename to aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java index 70e0c9934..fc6aecd6f 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java @@ -13,7 +13,9 @@ 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 + org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class, + org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class, + org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.class }) public class WebFluxTestApplication { diff --git a/aws-serverless-java-container-struts/pom.xml b/aws-serverless-java-container-struts/pom.xml deleted file mode 100644 index 9ba433706..000000000 --- a/aws-serverless-java-container-struts/pom.xml +++ /dev/null @@ -1,197 +0,0 @@ - - - 4.0.0 - - aws-serverless-java-container-struts - AWS Serverless Java container support - Struts implementation - Allows Java applications written for the Struts framework to run in AWS Lambda - https://aws.amazon.com/lambda - 1.10-SNAPSHOT - - - com.amazonaws.serverless - aws-serverless-java-container - 1.10-SNAPSHOT - - - - 6.0.3 - - - - - - com.amazonaws.serverless - aws-serverless-java-container-core - 1.10-SNAPSHOT - - - - org.apache.struts - struts2-core - ${struts.version} - - - commons-io - commons-io - - - - - org.apache.struts - struts2-json-plugin - ${struts.version} - test - - - - org.apache.struts - struts2-junit-plugin - ${struts.version} - test - - - - - commons-codec - commons-codec - 1.15 - test - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - - javax.el - javax.el-api - 2.2.5 - test - - - - javax.servlet - jsp-api - 2.0 - test - - - - org.glassfish.web - javax.el - 2.2.6 - test - - - - - - - commons-io - commons-io - 2.11.0 - - - org.apache.commons - commons-text - 1.10.0 - - - - - - - - org.jacoco - jacoco-maven-plugin - - ${basedir}/target/coverage-reports/jacoco-unit.exec - ${basedir}/target/coverage-reports/jacoco-unit.exec - - - - default-prepare-agent - - prepare-agent - - - - jacoco-site - package - - report - - - - jacoco-check - test - - check - - - true - - BUNDLE - - - INSTRUCTION - COVEREDRATIO - ${jacoco.minCoverage} - - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.9 - - always - - - - com.github.spotbugs - spotbugs-maven-plugin - - - - analyze-compile - compile - - check - - - - - - org.owasp - dependency-check-maven - ${dependencyCheck.version} - - true - - ${project.basedir}/../owasp-suppression.xml - - 7 - false - - - - - check - - - - - - - diff --git a/aws-serverless-java-container-struts/src/main/java/com/amazonaws/serverless/proxy/struts/StrutsLambdaContainerHandler.java b/aws-serverless-java-container-struts/src/main/java/com/amazonaws/serverless/proxy/struts/StrutsLambdaContainerHandler.java deleted file mode 100644 index 09c460cfa..000000000 --- a/aws-serverless-java-container-struts/src/main/java/com/amazonaws/serverless/proxy/struts/StrutsLambdaContainerHandler.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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.struts; - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.AwsHttpApiV2SecurityContextWriter; -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.AwsHttpApiV2HttpServletRequestReader; -import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest; -import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; -import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; -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; -import org.slf4j.LoggerFactory; - -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; - -/** - * A Lambda handler to initialize the Struts filter and proxy the requests. - * - * @param request type - * @param response type - */ -public class StrutsLambdaContainerHandler extends AwsLambdaServletContainerHandler { - - private static final Logger log = LoggerFactory.getLogger(StrutsLambdaContainerHandler.class); - - public static final String HEADER_STRUTS_STATUS_CODE = "X-Struts-StatusCode"; - - private static final String TIMER_STRUTS_CONTAINER_CONSTRUCTOR = "STRUTS_CONTAINER_CONSTRUCTOR"; - private static final String TIMER_STRUTS_HANDLE_REQUEST = "STRUTS_HANDLE_REQUEST"; - private static final String TIMER_STRUTS_COLD_START_INIT = "STRUTS_COLD_START_INIT"; - private static final String STRUTS_FILTER_NAME = "StrutsFilter"; - - private boolean initialized; - - public static StrutsLambdaContainerHandler getAwsProxyHandler() { - return new StrutsLambdaContainerHandler( - AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler()); - } - - public static StrutsLambdaContainerHandler getHttpApiV2ProxyHandler() { - return new StrutsLambdaContainerHandler( - HttpApiV2ProxyRequest.class, - AwsProxyResponse.class, - new AwsHttpApiV2HttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(true), - new AwsHttpApiV2SecurityContextWriter(), - new AwsProxyExceptionHandler()); - } - - public StrutsLambdaContainerHandler(Class requestTypeClass, - Class responseTypeClass, - RequestReader requestReader, - ResponseWriter responseWriter, - SecurityContextWriter securityContextWriter, - ExceptionHandler exceptionHandler) { - - super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); - Timer.start(TIMER_STRUTS_CONTAINER_CONSTRUCTOR); - this.initialized = false; - Timer.stop(TIMER_STRUTS_CONTAINER_CONSTRUCTOR); - } - - @Override - protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { - return new AwsHttpServletResponse(request, latch); - } - - @Override - protected void handleRequest(HttpServletRequest httpServletRequest, - AwsHttpServletResponse httpServletResponse, - Context lambdaContext) throws Exception { - Timer.start(TIMER_STRUTS_HANDLE_REQUEST); - if (!this.initialized) { - initialize(); - } - - 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) { - httpServletResponse.setStatus(Integer.parseInt(responseStatusCode)); - } - Timer.stop(TIMER_STRUTS_HANDLE_REQUEST); - } - - @Override - public void initialize() throws ContainerInitializationException { - log.info("Initialize Struts Lambda Application ..."); - Timer.start(TIMER_STRUTS_COLD_START_INIT); - try { - if (this.startupHandler != null) { - this.startupHandler.onStartup(this.getServletContext()); - } - StrutsPrepareAndExecuteFilter filter = new StrutsPrepareAndExecuteFilter(); - FilterRegistration.Dynamic filterRegistration = this.getServletContext() - .addFilter(STRUTS_FILTER_NAME, filter); - filterRegistration.addMappingForUrlPatterns( - EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.INCLUDE, DispatcherType.FORWARD), - true, "/*"); - } catch (Exception e) { - throw new ContainerInitializationException("Could not initialize Struts container", e); - } - - this.initialized = true; - Timer.stop(TIMER_STRUTS_COLD_START_INIT); - log.info("... initialize of Struts Lambda Application completed!"); - } - - public Servlet getServlet() { - return null; - } -} diff --git a/aws-serverless-java-container-struts/src/main/java/com/amazonaws/serverless/proxy/struts/StrutsLambdaHandler.java b/aws-serverless-java-container-struts/src/main/java/com/amazonaws/serverless/proxy/struts/StrutsLambdaHandler.java deleted file mode 100644 index 0e1ccfa17..000000000 --- a/aws-serverless-java-container-struts/src/main/java/com/amazonaws/serverless/proxy/struts/StrutsLambdaHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.struts; - -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * The lambda handler to handle the requests. - *

- * - * com.amazonaws.serverless.proxy.struts.StrutsLambdaHandler::handleRequest - * - */ -public class StrutsLambdaHandler implements RequestStreamHandler { - - private final StrutsLambdaContainerHandler handler = StrutsLambdaContainerHandler - .getAwsProxyHandler(); - - @Override - public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) - throws IOException { - handler.proxyStream(inputStream, outputStream, context); - } -} diff --git a/aws-serverless-java-container-struts/src/test/java/com/amazonaws/serverless/proxy/struts/StrutsAwsProxyTest.java b/aws-serverless-java-container-struts/src/test/java/com/amazonaws/serverless/proxy/struts/StrutsAwsProxyTest.java deleted file mode 100644 index eb890cf5b..000000000 --- a/aws-serverless-java-container-struts/src/test/java/com/amazonaws/serverless/proxy/struts/StrutsAwsProxyTest.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -package com.amazonaws.serverless.proxy.struts; - - -import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; -import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; -import com.amazonaws.serverless.proxy.struts.echoapp.EchoAction; -import com.amazonaws.services.lambda.runtime.Context; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.codec.binary.Base64; -import org.apache.struts2.StrutsRestTestCase; -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.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -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 StrutsAwsProxyTest extends StrutsRestTestCase { - 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 HTTP_METHOD_GET = "GET"; - private static final String QUERY_STRING_MODE = "mode"; - private static final String QUERY_STRING_KEY = "message"; - private static final String QUERY_STRING_ENCODED_VALUE = "Hello Struts2"; - private static final String USER_PRINCIPAL = "user1"; - private static final String CONTENT_TYPE_APPLICATION_JSON = "application/json; charset=UTF-8"; - - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private final StrutsLambdaContainerHandler handler = StrutsLambdaContainerHandler - .getAwsProxyHandler(); - private final StrutsLambdaContainerHandler httpApiHandler = StrutsLambdaContainerHandler - .getHttpApiV2ProxyHandler(); - private final Context lambdaContext = new MockLambdaContext(); - private final String type; - - public StrutsAwsProxyTest(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() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", HTTP_METHOD_GET) - .queryString(QUERY_STRING_MODE, "headers") - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .json(); - - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); - - validateMapResponseModel(output); - } - - @Test - public void context_servletResponse_setCustomHeader() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", HTTP_METHOD_GET) - .queryString("customHeader", "true") - .json(); - - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertTrue(output.getMultiValueHeaders().containsKey("XX")); - } - - @Test - public void context_serverInfo_correctContext() { - assumeTrue("API_GW".equals(type)); - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", HTTP_METHOD_GET) - .queryString(QUERY_STRING_KEY, "Hello Struts2") - .header("Content-Type", "application/json") - .queryString("contentType", "true"); - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); - - validateSingleValueModel(output, "Hello Struts2"); - } - - @Test - public void queryString_uriInfo_echo() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", HTTP_METHOD_GET) - .queryString(QUERY_STRING_MODE, "query-string") - .queryString(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .json(); - - - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); - - validateMapResponseModel(output); - } - - @Test - public void requestScheme_valid_expectHttps() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", HTTP_METHOD_GET) - .queryString(QUERY_STRING_MODE, "scheme") - .queryString(QUERY_STRING_KEY, QUERY_STRING_ENCODED_VALUE) - .json(); - - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); - - validateSingleValueModel(output, "https"); - } - - @Test - public void authorizer_securityContext_customPrincipalSuccess() { - assumeTrue("API_GW".equals(type)); - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", HTTP_METHOD_GET) - .queryString(QUERY_STRING_MODE, "principal") - .json() - .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID); - - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); - - validateSingleValueModel(output, AUTHORIZER_PRINCIPAL_ID); - } - - @Test - public void errors_unknownRoute_expect404() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/unknown", HTTP_METHOD_GET); - - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(404, output.getStatusCode()); - } - - @Test - public void error_contentType_invalidContentType() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "POST") - .queryString(QUERY_STRING_MODE, "content-type") - .header("Content-Type", "application/octet-stream") - .body("asdasdasd"); - - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(415, output.getStatusCode()); - } - - @Test - public void error_statusCode_methodNotAllowed() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "POST") - .queryString(QUERY_STRING_MODE, "not-allowed") - .json(); - - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(405, output.getStatusCode()); - } - - - @Test - public void responseBody_responseWriter_validBody() throws JsonProcessingException { - Map value = new HashMap<>(); - value.put(QUERY_STRING_KEY, CUSTOM_HEADER_VALUE); - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", "POST") - .json() - .body(OBJECT_MAPPER.writeValueAsString(value)); - - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertNotNull(output.getBody()); - - validateSingleValueModel(output, "{\"message\":\"my-custom-value\"}"); - } - - @Test - public void statusCode_responseStatusCode_customStatusCode() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", HTTP_METHOD_GET) - .queryString(QUERY_STRING_MODE, "custom-status-code") - .queryString("status", "201") - .json(); - - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(201, output.getStatusCode()); - } - - @Test - public void base64_binaryResponse_base64Encoding() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", HTTP_METHOD_GET); - - AwsProxyResponse response = executeRequest(request, lambdaContext); - assertNotNull(response.getBody()); - assertTrue(Base64.isBase64(response.getBody())); - } - - @Test - public void exception_mapException_mapToNotImplemented() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", "POST") - .queryString(QUERY_STRING_MODE, "not-implemented"); - - AwsProxyResponse response = executeRequest(request, lambdaContext); - assertNotNull(response.getBody()); - assertEquals("null", response.getBody()); - assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), response.getStatusCode()); - } - - @Test - public void stripBasePath_route_shouldRouteCorrectly() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/custompath/echo", HTTP_METHOD_GET) - .json() - .queryString(QUERY_STRING_KEY, "stripped"); - handler.stripBasePath("/custompath"); - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - validateSingleValueModel(output, "stripped"); - handler.stripBasePath(""); - } - - @Test - public void stripBasePath_route_shouldReturn404() { - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/custompath/echo/status-code", HTTP_METHOD_GET) - .json() - .queryString("status", "201"); - handler.stripBasePath("/custom"); - AwsProxyResponse output = executeRequest(request, lambdaContext); - assertEquals(404, output.getStatusCode()); - handler.stripBasePath(""); - } - - @Test - public void securityContext_injectPrincipal_expectPrincipalName() { - assumeTrue("API_GW".equals(type)); - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo-request-info", HTTP_METHOD_GET) - .queryString(QUERY_STRING_MODE, "principal") - .authorizerPrincipal(USER_PRINCIPAL); - - 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 { - decodedParam = URLDecoder.decode(paramValue, "UTF-8"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - fail("Could not decode parameter"); - } - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", HTTP_METHOD_GET).queryString(QUERY_STRING_KEY, decodedParam); - - 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"; - AwsProxyRequestBuilder request = new AwsProxyRequestBuilder("/echo", HTTP_METHOD_GET).queryString(QUERY_STRING_KEY, paramValue); - - AwsProxyResponse resp = executeRequest(request, lambdaContext); - assertEquals(200, resp.getStatusCode()); - validateSingleValueModel(resp, paramValue); - } - - - private void validateMapResponseModel(AwsProxyResponse output) { - validateMapResponseModel(output, CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); - } - - private void validateMapResponseModel(AwsProxyResponse output, String key, String value) { - try { - TypeReference> typeRef - = new TypeReference>() { - }; - HashMap response = OBJECT_MAPPER.readValue(output.getBody(), typeRef); - assertNotNull(response.get(key)); - assertEquals(value, response.get(key)); - } catch (IOException e) { - e.printStackTrace(); - fail("Exception while parsing response body: " + e.getMessage()); - } - } - - private void validateSingleValueModel(AwsProxyResponse output, String value) { - try { - assertNotNull(output.getBody()); - assertEquals(value, OBJECT_MAPPER.readerFor(String.class).readValue(output.getBody())); - } catch (Exception e) { - e.printStackTrace(); - fail("Exception while parsing response body: " + e.getMessage()); - } - } -} diff --git a/aws-serverless-java-container-struts/src/test/java/com/amazonaws/serverless/proxy/struts/echoapp/EchoAction.java b/aws-serverless-java-container-struts/src/test/java/com/amazonaws/serverless/proxy/struts/echoapp/EchoAction.java deleted file mode 100644 index d64a54eca..000000000 --- a/aws-serverless-java-container-struts/src/test/java/com/amazonaws/serverless/proxy/struts/echoapp/EchoAction.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.amazonaws.serverless.proxy.struts.echoapp; - -import com.opensymphony.xwork2.ActionSupport; -import org.apache.commons.io.IOUtils; -import org.apache.struts2.ServletActionContext; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - - -public class EchoAction extends ActionSupport { - - private String message; - - public String execute() throws IOException { - HttpServletRequest request = ServletActionContext.getRequest(); - - if (message == null && requestHasBody(request)) { - message = IOUtils.toString(request.getReader()); - } - - return SUCCESS; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public void setCustomHeader(boolean customHeader) { - if (customHeader) { - HttpServletResponse response = ServletActionContext.getResponse(); - response.setHeader("XX", "FOO"); - } - } - - - public void setContentType(boolean contentType) { - if (contentType) { - HttpServletResponse response = ServletActionContext.getResponse(); - response.setContentType("application/json"); - } - } - - private boolean requestHasBody(HttpServletRequest request) throws IOException { - return ("POST".equalsIgnoreCase(request.getMethod()) || "PUT".equalsIgnoreCase(request.getMethod())) && request.getReader() != null; - } - -} diff --git a/aws-serverless-java-container-struts/src/test/java/com/amazonaws/serverless/proxy/struts/echoapp/EchoRequestInfoAction.java b/aws-serverless-java-container-struts/src/test/java/com/amazonaws/serverless/proxy/struts/echoapp/EchoRequestInfoAction.java deleted file mode 100644 index 554156e9d..000000000 --- a/aws-serverless-java-container-struts/src/test/java/com/amazonaws/serverless/proxy/struts/echoapp/EchoRequestInfoAction.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.amazonaws.serverless.proxy.struts.echoapp; - -import com.amazonaws.serverless.proxy.RequestReader; -import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; -import com.opensymphony.xwork2.ActionSupport; -import org.apache.struts2.ServletActionContext; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; - - -public class EchoRequestInfoAction extends ActionSupport { - - private String mode = "principal"; - private Object result = null; - - public String execute() { - - HttpServletRequest request = ServletActionContext.getRequest(); - AwsProxyRequestContext awsProxyRequestContext = - (AwsProxyRequestContext) request - .getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY); - - switch (mode) { - case "principal": - result = awsProxyRequestContext.getAuthorizer().getPrincipalId(); - break; - case "scheme": - result = request.getScheme(); - break; - case "content-type": - if (request.getContentType().contains("application/octet-stream")) { - ServletActionContext.getResponse().setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE); - } - result = request.getContentType(); - break; - case "not-allowed": - ServletActionContext.getResponse().setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - break; - case "custom-status-code": - ServletActionContext.getResponse().setStatus(HttpServletResponse.SC_CREATED); - break; - case "not-implemented": - ServletActionContext.getResponse().setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - break; - case "headers": - Map headers = new HashMap<>(); - - Enumeration headerNames = request.getHeaderNames(); - while (headerNames.hasMoreElements()) { - String headerName = headerNames.nextElement(); - headers.put(headerName, request.getHeader(headerName)); - } - - result = headers; - break; - case "query-string": - Map params = new HashMap<>(); - - Enumeration parameterNames = request.getParameterNames(); - while (parameterNames.hasMoreElements()) { - String parameterName = parameterNames.nextElement(); - params.put(parameterName, request.getParameter(parameterName)); - } - - result = params; - break; - default: - throw new IllegalArgumentException("Invalid mode requested: " + mode); - } - - return SUCCESS; - } - - public Object getResult() { - return result; - } - - public void setMode(String mode) { - this.mode = mode; - } -} diff --git a/aws-serverless-java-container-struts/src/test/resources/log4j2.xml b/aws-serverless-java-container-struts/src/test/resources/log4j2.xml deleted file mode 100644 index 55ed0d21c..000000000 --- a/aws-serverless-java-container-struts/src/test/resources/log4j2.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n - - - - - - - - - - - \ No newline at end of file diff --git a/aws-serverless-java-container-struts/src/test/resources/struts.xml b/aws-serverless-java-container-struts/src/test/resources/struts.xml deleted file mode 100644 index 92070be83..000000000 --- a/aws-serverless-java-container-struts/src/test/resources/struts.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - message - - - - - - - - result - - - - - - - \ No newline at end of file diff --git a/aws-serverless-jersey-archetype/pom.xml b/aws-serverless-jersey-archetype/pom.xml index c13c05869..4afdc9996 100644 --- a/aws-serverless-jersey-archetype/pom.xml +++ b/aws-serverless-jersey-archetype/pom.xml @@ -4,16 +4,16 @@ com.amazonaws.serverless aws-serverless-java-container - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT com.amazonaws.serverless.archetypes aws-serverless-jersey-archetype - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT maven-archetype - https://github.com/awslabs/aws-serverless-java-container.git + https://github.com/aws/serverless-java-container.git HEAD @@ -48,7 +48,7 @@ org.apache.maven.archetype archetype-packaging - 3.0.1 + 3.4.0 @@ -57,7 +57,7 @@ org.apache.maven.plugins maven-resources-plugin - 3.1.0 + 3.3.1 \ @@ -65,7 +65,7 @@ org.apache.maven.plugins maven-archetype-plugin - 3.0.1 + 3.4.0 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 1017a83ad..311c40aee 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 @@ -16,7 +16,7 @@ #set($resourceName = "#replaceChar($resourceName, '.')") #set($resourceName = $resourceName.replaceAll("\n", "").trim()) # \${artifactId} serverless API -The \${artifactId} project, created with [`aws-serverless-java-container`](https://github.com/awslabs/aws-serverless-java-container). +The \${artifactId} project, created with [`aws-serverless-java-container`](https://github.com/aws/serverless-java-container). The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/build.gradle index 381bc42e6..34ddfa299 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/build.gradle +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/build.gradle @@ -7,23 +7,25 @@ repositories { dependencies { implementation ( - 'com.amazonaws:aws-lambda-java-core:1.2.1', - 'com.amazonaws.serverless:aws-serverless-java-container-jersey:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.14.0', - 'io.symphonia:lambda-logging:1.0.3' + 'com.amazonaws.serverless:aws-serverless-java-container-jersey:[2.0-SNAPSHOT,)', + 'com.fasterxml.jackson.core:jackson-databind:2.19.1', ) - implementation("org.glassfish.jersey.media:jersey-media-json-jackson:2.37") { + implementation("org.glassfish.jersey.media:jersey-media-json-jackson:3.1.10") { 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" } - implementation("org.glassfish.jersey.inject:jersey-hk2:2.37") { + implementation("org.glassfish.jersey.inject:jersey-hk2:3.1.10") { exclude group: 'javax.inject', module: "javax.inject" } - testImplementation("junit:junit:4.13.2") + testImplementation("com.amazonaws.serverless:aws-serverless-java-container-core:[2.0-SNAPSHOT,):tests") + testImplementation("org.apache.httpcomponents.client5:httpclient5:5.5") + testImplementation(platform("org.junit:junit-bom:5.13.1")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } task buildZip(type: Zip) { @@ -34,4 +36,8 @@ task buildZip(type: Zip) { } } +test { + useJUnitPlatform() +} + build.dependsOn buildZip 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 2ae636fa1..1f05e9018 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 @@ -9,13 +9,15 @@ jar Serverless Jersey API - https://github.com/awslabs/aws-serverless-java-container + https://github.com/aws/serverless-java-container 1.8 1.8 - 2.37 - 2.14.0 + + 3.1.10 + 2.18.3 + 5.12.1 @@ -24,6 +26,20 @@ aws-serverless-java-container-jersey ${project.version} + + com.amazonaws.serverless + aws-serverless-java-container-core + ${project.version} + tests + test-jar + test + + + org.apache.httpcomponents.client5 + httpclient5 + 5.4.3 + test + org.glassfish.jersey.media @@ -65,13 +81,24 @@ - junit - junit - 4.12 + org.junit.jupiter + junit-jupiter test + + + + org.junit + junit-bom + ${junit.version} + import + pom + + + + shaded-jar @@ -80,7 +107,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 false @@ -107,7 +134,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.4.2 default-jar @@ -118,7 +145,7 @@ org.apache.maven.plugins maven-install-plugin - 3.0.0-M1 + 3.1.2 true @@ -127,7 +154,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.2.0 + 3.8.1 copy-dependencies @@ -145,7 +172,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.3.0 + 3.7.1 zip-assembly diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/resource/PingResource.java b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/resource/PingResource.java index 137ed0302..1c56254fd 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/resource/PingResource.java +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/resource/PingResource.java @@ -4,13 +4,13 @@ import java.util.Map; import java.util.HashMap; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; @Path("/ping") public class PingResource { diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java index 3f232ebc6..53db9b161 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java @@ -7,19 +7,19 @@ import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.services.lambda.runtime.Context; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class StreamLambdaHandlerTest { @@ -27,7 +27,7 @@ public class StreamLambdaHandlerTest { private static StreamLambdaHandler handler; private static Context lambdaContext; - @BeforeClass + @BeforeAll public static void setUp() { handler = new StreamLambdaHandler(); lambdaContext = new MockLambdaContext(); diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/template.yml b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/template.yml index e6e7e59b3..0ee7360dd 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/template.yml +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/template.yml @@ -32,7 +32,7 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: ${groupId}.StreamLambdaHandler::handleRequest - Runtime: java11 + Runtime: java21 CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole 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 deleted file mode 100644 index 2ea9dca13..000000000 --- a/aws-serverless-spark-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - src/main/java - - **/*.java - - - - src/test/java - - **/*.java - - - - src/assembly - - * - - - - - - template.yml - README.md - build.gradle - - - - \ No newline at end of file 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 deleted file mode 100644 index 95cf50f90..000000000 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -apply plugin: 'java' - -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - implementation ( - 'com.sparkjava:spark-core:2.9.4', - 'com.amazonaws.serverless:aws-serverless-java-container-spark:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.14.0', - 'io.symphonia:lambda-logging:1.0.3' - ) - - testImplementation("junit:junit:4.13.2") -} - -task buildZip(type: Zip) { - from compileJava - from processResources - into('lib') { - from(configurations.compileClasspath) { - exclude 'jetty-http*' - exclude 'jetty-client*' - exclude 'jetty-webapp*' - exclude 'jetty-xml*' - exclude 'jetty-io*' - exclude 'websocket*' - } - } -} - -build.dependsOn buildZip diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/main/java/SparkResources.java b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/main/java/SparkResources.java deleted file mode 100644 index bc55eb43d..000000000 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/main/java/SparkResources.java +++ /dev/null @@ -1,25 +0,0 @@ -package ${groupId}; - - -import java.util.HashMap; -import java.util.Map; - -import static spark.Spark.before; -import static spark.Spark.get; - -import ${groupId}.util.JsonTransformer; - - -public class SparkResources { - - public static void defineResources() { - before((request, response) -> response.type("application/json")); - - get("/ping", (req, res) -> { - Map pong = new HashMap<>(); - pong.put("pong", "Hello, World!"); - res.status(200); - return pong; - }, new JsonTransformer()); - } -} \ No newline at end of file diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java deleted file mode 100644 index fdd1409a5..000000000 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -package ${groupId}; - - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.spark.SparkLambdaContainerHandler; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; - -import spark.Spark; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - - -public class StreamLambdaHandler implements RequestStreamHandler { - private static SparkLambdaContainerHandler handler; - static { - try { - handler = SparkLambdaContainerHandler.getAwsProxyHandler(); - SparkResources.defineResources(); - Spark.awaitInitialization(); - } catch (ContainerInitializationException e) { - // if we fail here. We re-throw the exception to force another cold start - e.printStackTrace(); - throw new RuntimeException("Could not initialize Spark container", e); - } - } - - @Override - public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) - throws IOException { - handler.proxyStream(inputStream, outputStream, context); - } -} diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/main/java/util/JsonTransformer.java b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/main/java/util/JsonTransformer.java deleted file mode 100644 index 8df085ea1..000000000 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/main/java/util/JsonTransformer.java +++ /dev/null @@ -1,24 +0,0 @@ -package ${groupId}.util; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spark.ResponseTransformer; - -public class JsonTransformer implements ResponseTransformer { - - private ObjectMapper mapper = new ObjectMapper(); - private Logger log = LoggerFactory.getLogger(JsonTransformer.class); - - @Override - public String render(Object model) { - try { - return mapper.writeValueAsString(model); - } catch (JsonProcessingException e) { - log.error("Cannot serialize object", e); - return null; - } - } - -} \ No newline at end of file diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/template.yml b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/template.yml deleted file mode 100644 index d45c9923b..000000000 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/template.yml +++ /dev/null @@ -1,52 +0,0 @@ -#set($resourceName = $artifactId) -#macro(replaceChar $originalName, $char) - #if($originalName.contains($char)) - #set($tokens = $originalName.split($char)) - #set($newResourceName = "") - #foreach($token in $tokens) - #set($newResourceName = $newResourceName + $token.substring(0,1).toUpperCase() + $token.substring(1).toLowerCase()) - #end - ${newResourceName} - #else - #set($newResourceName = $originalName.substring(0,1).toUpperCase() + $originalName.substring(1)) - ${newResourceName} - #end -#end -#set($resourceName = "#replaceChar($resourceName, '-')") -#set($resourceName = "#replaceChar($resourceName, '.')") -#set($resourceName = $resourceName.replaceAll("\n", "").trim()) -#macro(regionVar) - AWS::Region -#end -#set($awsRegion = "#regionVar()") -#set($awsRegion = $awsRegion.replaceAll("\n", "").trim()) -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: AWS Serverless Spark API - ${groupId}::${artifactId} -Globals: - Api: - EndpointConfiguration: REGIONAL - -Resources: - ${resourceName}Function: - Type: AWS::Serverless::Function - Properties: - Handler: ${groupId}.StreamLambdaHandler::handleRequest - Runtime: java11 - CodeUri: . - MemorySize: 512 - Policies: AWSLambdaBasicExecutionRole - Timeout: 15 - Events: - ProxyResource: - Type: Api - Properties: - Path: /{proxy+} - Method: any - -Outputs: - ${resourceName}Api: - Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${${awsRegion}}.amazonaws.com/Prod/ping' - Export: - Name: ${resourceName}Api diff --git a/aws-serverless-spark-archetype/src/test/resources/projects/base/archetype.properties b/aws-serverless-spark-archetype/src/test/resources/projects/base/archetype.properties deleted file mode 100644 index 33825ed03..000000000 --- a/aws-serverless-spark-archetype/src/test/resources/projects/base/archetype.properties +++ /dev/null @@ -1,3 +0,0 @@ -groupId=test.service -artifactId=spark-archetype-test -version=1.0-SNAPSHOT diff --git a/aws-serverless-spring-archetype/pom.xml b/aws-serverless-spring-archetype/pom.xml index 83dc79743..2a2f59b8b 100644 --- a/aws-serverless-spring-archetype/pom.xml +++ b/aws-serverless-spring-archetype/pom.xml @@ -4,16 +4,16 @@ com.amazonaws.serverless aws-serverless-java-container - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT com.amazonaws.serverless.archetypes aws-serverless-spring-archetype - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT maven-archetype - https://github.com/awslabs/aws-serverless-java-container.git + https://github.com/aws/serverless-java-container.git HEAD @@ -48,7 +48,7 @@ org.apache.maven.archetype archetype-packaging - 3.0.1 + 3.4.0 @@ -57,7 +57,7 @@ org.apache.maven.plugins maven-resources-plugin - 3.1.0 + 3.3.1 \ @@ -66,7 +66,7 @@ org.apache.maven.plugins maven-archetype-plugin - 3.0.1 + 3.4.0 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 1017a83ad..311c40aee 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 @@ -16,7 +16,7 @@ #set($resourceName = "#replaceChar($resourceName, '.')") #set($resourceName = $resourceName.replaceAll("\n", "").trim()) # \${artifactId} serverless API -The \${artifactId} project, created with [`aws-serverless-java-container`](https://github.com/awslabs/aws-serverless-java-container). +The \${artifactId} project, created with [`aws-serverless-java-container`](https://github.com/aws/serverless-java-container). The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. 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 3e53cd649..9ba701351 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,17 +7,21 @@ repositories { dependencies { implementation ( - 'org.springframework:spring-webmvc:5.3.23', - 'org.springframework:spring-context:5.3.23', - 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', - 'org.apache.logging.log4j:log4j-core:2.19.0', - 'org.apache.logging.log4j:log4j-api:2.19.0', - 'org.apache.logging.log4j:log4j-slf4j-impl:2.19.0', - 'com.fasterxml.jackson.core:jackson-databind:2.14.0', - 'com.amazonaws:aws-lambda-java-log4j2:1.5.1', + 'org.springframework:spring-webmvc:6.2.8', + 'org.springframework:spring-context:6.2.8', + 'com.amazonaws.serverless:aws-serverless-java-container-spring:[2.0-SNAPSHOT,)', + 'org.apache.logging.log4j:log4j-core:2.24.3', + 'org.apache.logging.log4j:log4j-api:2.24.3', + 'org.apache.logging.log4j:log4j-slf4j-impl:2.24.3', + 'com.fasterxml.jackson.core:jackson-databind:2.19.1', + 'com.amazonaws:aws-lambda-java-log4j2:1.6.0', ) - testImplementation("junit:junit:4.13.2") + testImplementation("com.amazonaws.serverless:aws-serverless-java-container-core:[2.0-SNAPSHOT,):tests") + testImplementation("org.apache.httpcomponents.client5:httpclient5:5.5") + testImplementation(platform("org.junit:junit-bom:5.13.1")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } task buildZip(type: Zip) { @@ -28,4 +32,8 @@ task buildZip(type: Zip) { } } +test { + useJUnitPlatform() +} + build.dependsOn buildZip 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 5491fbb62..48b70f27f 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 @@ -11,14 +11,14 @@ jar Serverless Spring API - https://github.com/awslabs/aws-serverless-java-container + https://github.com/aws/serverless-java-container 1.8 1.8 - 5.3.23 - 4.13.2 - 2.19.0 + 6.2.6 + 5.12.1 + 2.24.2 @@ -27,6 +27,20 @@ aws-serverless-java-container-spring ${project.version} + + com.amazonaws.serverless + aws-serverless-java-container-core + ${project.version} + tests + test-jar + test + + + org.apache.httpcomponents.client5 + httpclient5 + 5.4.3 + test + org.springframework @@ -74,18 +88,29 @@ com.amazonaws aws-lambda-java-log4j2 - 1.5.1 + 1.6.0 - junit - junit - \${junit.version} + org.junit.jupiter + junit-jupiter test + + + + org.junit + junit-bom + ${junit.version} + import + pom + + + + shaded-jar @@ -94,7 +119,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 package @@ -102,10 +127,9 @@ shade - false + implementation="io.github.edwgiz.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer"> @@ -113,9 +137,9 @@ - com.github.edwgiz - maven-shade-plugin.log4j2-cachefile-transformer - 2.8.1 + io.github.edwgiz + log4j-maven-shade-plugin-extensions + 2.20.0 @@ -133,7 +157,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.4.2 default-jar @@ -144,7 +168,7 @@ org.apache.maven.plugins maven-install-plugin - 3.0.0-M1 + 3.1.2 true @@ -153,7 +177,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.2.0 + 3.8.1 copy-dependencies @@ -171,7 +195,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.3.0 + 3.7.1 zip-assembly diff --git a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/main/java/SpringApiConfig.java b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/main/java/SpringApiConfig.java index 0982c6b04..83926b5dd 100644 --- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/main/java/SpringApiConfig.java +++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/main/java/SpringApiConfig.java @@ -11,8 +11,8 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import ${groupId}.controller.PingController; diff --git a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java index 3f232ebc6..53db9b161 100644 --- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java +++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java @@ -7,19 +7,19 @@ import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.services.lambda.runtime.Context; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class StreamLambdaHandlerTest { @@ -27,7 +27,7 @@ public class StreamLambdaHandlerTest { private static StreamLambdaHandler handler; private static Context lambdaContext; - @BeforeClass + @BeforeAll public static void setUp() { handler = new StreamLambdaHandler(); lambdaContext = new MockLambdaContext(); diff --git a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/template.yml b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/template.yml index 1c919d023..fe49737e5 100644 --- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/template.yml +++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/template.yml @@ -32,7 +32,7 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: ${groupId}.StreamLambdaHandler::handleRequest - Runtime: java11 + Runtime: java21 CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole diff --git a/aws-serverless-springboot2-archetype/pom.xml b/aws-serverless-springboot2-archetype/pom.xml deleted file mode 100644 index 1e7279dbf..000000000 --- a/aws-serverless-springboot2-archetype/pom.xml +++ /dev/null @@ -1,80 +0,0 @@ - - 4.0.0 - - - com.amazonaws.serverless - aws-serverless-java-container - 1.10-SNAPSHOT - - - com.amazonaws.serverless.archetypes - aws-serverless-springboot2-archetype - 1.10-SNAPSHOT - maven-archetype - - - https://github.com/awslabs/aws-serverless-java-container.git - HEAD - - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - - - src/main/resources - true - - archetype-resources/pom.xml - archetype-resources/README.md - - - - src/main/resources - false - - archetype-resources/pom.xml - - - - - - - org.apache.maven.archetype - archetype-packaging - 3.0.1 - - - - - - - org.apache.maven.plugins - maven-resources-plugin - 3.1.0 - - \ - - - - org.apache.maven.plugins - maven-archetype-plugin - 3.0.1 - - - - integration-test - - - - - - - - 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 deleted file mode 100644 index 1017a83ad..000000000 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/README.md +++ /dev/null @@ -1,99 +0,0 @@ -#set($resourceName = $artifactId) -#macro(replaceChar $originalName, $char) - #if($originalName.contains($char)) - #set($tokens = $originalName.split($char)) - #set($newResourceName = "") - #foreach($token in $tokens) - #set($newResourceName = $newResourceName + $token.substring(0,1).toUpperCase() + $token.substring(1).toLowerCase()) - #end - ${newResourceName} - #else - #set($newResourceName = $originalName.substring(0,1).toUpperCase() + $originalName.substring(1)) - ${newResourceName} - #end -#end -#set($resourceName = "#replaceChar($resourceName, '-')") -#set($resourceName = "#replaceChar($resourceName, '.')") -#set($resourceName = $resourceName.replaceAll("\n", "").trim()) -# \${artifactId} serverless API -The \${artifactId} project, created with [`aws-serverless-java-container`](https://github.com/awslabs/aws-serverless-java-container). - -The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. - -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). - -#[[##]]# 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} -$ sam build -Building resource '\${resourceName}Function' -Running JavaGradleWorkflow:GradleBuild -Running JavaGradleWorkflow:CopyArtifacts - -Build Succeeded - -Built Artifacts : .aws-sam/build -Built Template : .aws-sam/build/template.yaml - -Commands you can use next -========================= -[*] Invoke Function: sam local invoke -[*] Deploy: sam deploy --guided -``` - -#[[##]]# 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 - -... -Mounting ${groupId}.StreamLambdaHandler::handleRequest (java11) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] -... -``` - -Using a new shell, you can send a test ping request to your API: - -```bash -$ curl -s http://127.0.0.1:3000/ping | python -m json.tool - -{ - "pong": "Hello, World!" -} -``` - -#[[##]]# 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 - -``` -$ sam deploy --guided -``` - -Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL - -``` -... -------------------------------------------------------------------------------------------------------------- -OutputKey-Description OutputValue -------------------------------------------------------------------------------------------------------------- -\${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: - -```bash -$ curl -s https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping | python -m json.tool - -{ - "pong": "Hello, World!" -} -``` diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java deleted file mode 100644 index 3f232ebc6..000000000 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package ${groupId}; - - -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.AwsProxyResponse; -import com.amazonaws.services.lambda.runtime.Context; - -import org.junit.BeforeClass; -import org.junit.Test; - -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import static org.junit.Assert.*; - - -public class StreamLambdaHandlerTest { - - private static StreamLambdaHandler handler; - private static Context lambdaContext; - - @BeforeClass - public static void setUp() { - handler = new StreamLambdaHandler(); - lambdaContext = new MockLambdaContext(); - } - - @Test - public void ping_streamRequest_respondsWithHello() { - InputStream requestStream = new AwsProxyRequestBuilder("/ping", HttpMethod.GET) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) - .buildStream(); - ByteArrayOutputStream responseStream = new ByteArrayOutputStream(); - - handle(requestStream, responseStream); - - AwsProxyResponse response = readResponse(responseStream); - assertNotNull(response); - assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode()); - - assertFalse(response.isBase64Encoded()); - - assertTrue(response.getBody().contains("pong")); - assertTrue(response.getBody().contains("Hello, World!")); - - assertTrue(response.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); - assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON)); - } - - @Test - public void invalidResource_streamRequest_responds404() { - InputStream requestStream = new AwsProxyRequestBuilder("/pong", HttpMethod.GET) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) - .buildStream(); - ByteArrayOutputStream responseStream = new ByteArrayOutputStream(); - - handle(requestStream, responseStream); - - AwsProxyResponse response = readResponse(responseStream); - assertNotNull(response); - assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatusCode()); - } - - private void handle(InputStream is, ByteArrayOutputStream os) { - try { - handler.handleRequest(is, os, lambdaContext); - } catch (IOException e) { - e.printStackTrace(); - fail(e.getMessage()); - } - } - - private AwsProxyResponse readResponse(ByteArrayOutputStream responseStream) { - try { - return LambdaContainerHandler.getObjectMapper().readValue(responseStream.toByteArray(), AwsProxyResponse.class); - } catch (IOException e) { - e.printStackTrace(); - fail("Error while parsing response: " + e.getMessage()); - } - return null; - } -} diff --git a/aws-serverless-springboot2-archetype/src/test/resources/projects/base/goal.txt b/aws-serverless-springboot2-archetype/src/test/resources/projects/base/goal.txt deleted file mode 100644 index 597acc768..000000000 --- a/aws-serverless-springboot2-archetype/src/test/resources/projects/base/goal.txt +++ /dev/null @@ -1 +0,0 @@ -package \ No newline at end of file diff --git a/aws-serverless-spark-archetype/pom.xml b/aws-serverless-springboot3-archetype/pom.xml similarity index 87% rename from aws-serverless-spark-archetype/pom.xml rename to aws-serverless-springboot3-archetype/pom.xml index ca3ad3952..74fac6156 100644 --- a/aws-serverless-spark-archetype/pom.xml +++ b/aws-serverless-springboot3-archetype/pom.xml @@ -4,16 +4,16 @@ com.amazonaws.serverless aws-serverless-java-container - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT com.amazonaws.serverless.archetypes - aws-serverless-spark-archetype - 1.10-SNAPSHOT + aws-serverless-springboot3-archetype + 2.1.5-SNAPSHOT maven-archetype - https://github.com/awslabs/aws-serverless-java-container.git + https://github.com/aws/serverless-java-container.git HEAD @@ -48,7 +48,7 @@ org.apache.maven.archetype archetype-packaging - 3.0.1 + 3.4.0 @@ -57,16 +57,15 @@ org.apache.maven.plugins maven-resources-plugin - 3.1.0 + 3.3.1 \ - org.apache.maven.plugins maven-archetype-plugin - 3.0.1 + 3.4.0 diff --git a/aws-serverless-springboot2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/aws-serverless-springboot3-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml similarity index 100% rename from aws-serverless-springboot2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml rename to aws-serverless-springboot3-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/README.md similarity index 98% rename from aws-serverless-spark-archetype/src/main/resources/archetype-resources/README.md rename to aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/README.md index 1017a83ad..311c40aee 100644 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/README.md +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/README.md @@ -16,7 +16,7 @@ #set($resourceName = "#replaceChar($resourceName, '.')") #set($resourceName = $resourceName.replaceAll("\n", "").trim()) # \${artifactId} serverless API -The \${artifactId} project, created with [`aws-serverless-java-container`](https://github.com/awslabs/aws-serverless-java-container). +The \${artifactId} project, created with [`aws-serverless-java-container`](https://github.com/aws/serverless-java-container). The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. diff --git a/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/build.gradle new file mode 100644 index 000000000..3aa54825c --- /dev/null +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/build.gradle @@ -0,0 +1,37 @@ +apply plugin: 'java' + +repositories { + mavenLocal() + mavenCentral() + maven {url "https://repo.spring.io/milestone"} + maven {url "https://repo.spring.io/snapshot"} +} + +dependencies { + implementation ( + 'org.springframework.boot:spring-boot-starter-web:3.4.5', + 'com.amazonaws.serverless:aws-serverless-java-container-springboot3:[2.0-SNAPSHOT,)', + ) + + testImplementation("com.amazonaws.serverless:aws-serverless-java-container-core:[2.0-SNAPSHOT,):tests") + testImplementation("org.apache.httpcomponents.client5:httpclient5:5.5") + testImplementation(platform("org.junit:junit-bom:5.13.1")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +task buildZip(type: Zip) { + from compileJava + from processResources + into('lib') { + from(configurations.compileClasspath) { + exclude 'tomcat-embed-*' + } + } +} + +test { + useJUnitPlatform() +} + +build.dependsOn buildZip diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/pom.xml similarity index 79% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/pom.xml rename to aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/pom.xml index 9a0b4123a..73d407dd8 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/pom.xml @@ -10,26 +10,40 @@ \${version} jar - Serverless Spring Boot 2 API - https://github.com/awslabs/aws-serverless-java-container + Serverless Spring Boot 3 API + https://github.com/aws/serverless-java-container org.springframework.boot spring-boot-starter-parent - 2.7.5 + 3.4.5 - 1.8 - 1.8 + 17 + 5.12.1 com.amazonaws.serverless - aws-serverless-java-container-springboot2 + aws-serverless-java-container-springboot3 ${project.version} + + com.amazonaws.serverless + aws-serverless-java-container-core + ${project.version} + tests + test-jar + test + + + org.apache.httpcomponents.client5 + httpclient5 + 5.4.3 + test + org.springframework.boot @@ -43,13 +57,24 @@ - junit - junit - 4.12 + org.junit.jupiter + junit-jupiter test + + + + org.junit + junit-bom + ${junit.version} + import + pom + + + + shaded-jar @@ -58,7 +83,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 false @@ -92,7 +117,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.4.2 default-jar @@ -103,7 +128,7 @@ org.apache.maven.plugins maven-install-plugin - 3.0.0-M1 + 3.1.2 true @@ -112,7 +137,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.2.0 + 3.8.1 copy-dependencies @@ -130,7 +155,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.3.0 + 3.7.1 zip-assembly diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml similarity index 100% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml rename to aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/Application.java b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/main/java/Application.java similarity index 100% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/Application.java rename to aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/main/java/Application.java diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java similarity index 76% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java rename to aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java index dca9650d3..e022540c1 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java @@ -18,12 +18,6 @@ public class StreamLambdaHandler implements RequestStreamHandler { static { try { handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class); - // For applications that take longer than 10 seconds to start, use the async builder: - // handler = new SpringBootProxyHandlerBuilder() - // .defaultProxy() - // .asyncInit() - // .springBootApplication(Application.class) - // .buildAndInitialize(); } catch (ContainerInitializationException e) { // if we fail here. We re-throw the exception to force another cold start e.printStackTrace(); diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/controller/PingController.java b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/main/java/controller/PingController.java similarity index 100% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/controller/PingController.java rename to aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/main/java/controller/PingController.java diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties similarity index 57% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties rename to aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties index ec1cb9792..070e632fe 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties @@ -1,3 +1,3 @@ # Reduce logging level to make sure the application works with SAM local -# https://github.com/awslabs/aws-serverless-java-container/issues/134 +# https://github.com/aws/serverless-java-container/issues/134 logging.level.root=WARN \ No newline at end of file diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java similarity index 91% rename from aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java rename to aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java index 3f232ebc6..26d5360bf 100644 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java @@ -7,27 +7,26 @@ import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.services.lambda.runtime.Context; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import static org.junit.Assert.*; - +import static org.junit.jupiter.api.Assertions.*; public class StreamLambdaHandlerTest { private static StreamLambdaHandler handler; private static Context lambdaContext; - @BeforeClass + @BeforeAll public static void setUp() { handler = new StreamLambdaHandler(); lambdaContext = new MockLambdaContext(); diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/template.yml b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/template.yml similarity index 98% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/template.yml rename to aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/template.yml index 8d8e0946a..fe96b1fa2 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/template.yml +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/template.yml @@ -32,7 +32,7 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: ${groupId}.StreamLambdaHandler::handleRequest - Runtime: java11 + Runtime: java21 CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole diff --git a/aws-serverless-springboot2-archetype/src/test/resources/projects/base/archetype.properties b/aws-serverless-springboot3-archetype/src/test/resources/projects/base/archetype.properties similarity index 100% rename from aws-serverless-springboot2-archetype/src/test/resources/projects/base/archetype.properties rename to aws-serverless-springboot3-archetype/src/test/resources/projects/base/archetype.properties diff --git a/aws-serverless-spark-archetype/src/test/resources/projects/base/goal.txt b/aws-serverless-springboot3-archetype/src/test/resources/projects/base/goal.txt similarity index 100% rename from aws-serverless-spark-archetype/src/test/resources/projects/base/goal.txt rename to aws-serverless-springboot3-archetype/src/test/resources/projects/base/goal.txt diff --git a/aws-serverless-struts-archetype/pom.xml b/aws-serverless-struts-archetype/pom.xml deleted file mode 100644 index b91cfa4bd..000000000 --- a/aws-serverless-struts-archetype/pom.xml +++ /dev/null @@ -1,80 +0,0 @@ - - 4.0.0 - - - com.amazonaws.serverless - aws-serverless-java-container - 1.10-SNAPSHOT - - - com.amazonaws.serverless.archetypes - aws-serverless-struts-archetype - 1.10-SNAPSHOT - maven-archetype - - - https://github.com/awslabs/aws-serverless-java-container.git - HEAD - - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - - - src/main/resources - true - - archetype-resources/pom.xml - archetype-resources/README.md - - - - src/main/resources - false - - archetype-resources/pom.xml - - - - - - - org.apache.maven.archetype - archetype-packaging - 3.0.1 - - - - - - - org.apache.maven.plugins - maven-resources-plugin - 3.1.0 - - \ - - - - org.apache.maven.plugins - maven-archetype-plugin - 3.0.1 - - - - integration-test - - - - - - - - diff --git a/aws-serverless-struts-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/aws-serverless-struts-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml deleted file mode 100644 index ad8b86248..000000000 --- a/aws-serverless-struts-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - src/main/java - - **/*.java - - - - src/test/java - - **/*.java - - - - src/main/resources - - **/*.properties - **/*.xml - - - - src/main/assembly - - **/*.xml - - - - - - template.yml - README.md - build.gradle - - - - \ No newline at end of file diff --git a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-struts-archetype/src/main/resources/archetype-resources/README.md deleted file mode 100644 index 1017a83ad..000000000 --- a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/README.md +++ /dev/null @@ -1,99 +0,0 @@ -#set($resourceName = $artifactId) -#macro(replaceChar $originalName, $char) - #if($originalName.contains($char)) - #set($tokens = $originalName.split($char)) - #set($newResourceName = "") - #foreach($token in $tokens) - #set($newResourceName = $newResourceName + $token.substring(0,1).toUpperCase() + $token.substring(1).toLowerCase()) - #end - ${newResourceName} - #else - #set($newResourceName = $originalName.substring(0,1).toUpperCase() + $originalName.substring(1)) - ${newResourceName} - #end -#end -#set($resourceName = "#replaceChar($resourceName, '-')") -#set($resourceName = "#replaceChar($resourceName, '.')") -#set($resourceName = $resourceName.replaceAll("\n", "").trim()) -# \${artifactId} serverless API -The \${artifactId} project, created with [`aws-serverless-java-container`](https://github.com/awslabs/aws-serverless-java-container). - -The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. - -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). - -#[[##]]# 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} -$ sam build -Building resource '\${resourceName}Function' -Running JavaGradleWorkflow:GradleBuild -Running JavaGradleWorkflow:CopyArtifacts - -Build Succeeded - -Built Artifacts : .aws-sam/build -Built Template : .aws-sam/build/template.yaml - -Commands you can use next -========================= -[*] Invoke Function: sam local invoke -[*] Deploy: sam deploy --guided -``` - -#[[##]]# 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 - -... -Mounting ${groupId}.StreamLambdaHandler::handleRequest (java11) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] -... -``` - -Using a new shell, you can send a test ping request to your API: - -```bash -$ curl -s http://127.0.0.1:3000/ping | python -m json.tool - -{ - "pong": "Hello, World!" -} -``` - -#[[##]]# 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 - -``` -$ sam deploy --guided -``` - -Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL - -``` -... -------------------------------------------------------------------------------------------------------------- -OutputKey-Description OutputValue -------------------------------------------------------------------------------------------------------------- -\${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: - -```bash -$ curl -s https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping | python -m json.tool - -{ - "pong": "Hello, World!" -} -``` diff --git a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-struts-archetype/src/main/resources/archetype-resources/build.gradle deleted file mode 100644 index d37db592f..000000000 --- a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/build.gradle +++ /dev/null @@ -1,58 +0,0 @@ -apply plugin: 'java' - -repositories { - mavenLocal() - mavenCentral() -} - -configurations { - implementation { - exclude group: 'org.apache.logging.log4j', module: 'log4j-to-slf4j' - } -} - -dependencies { - - implementation ('com.amazonaws.serverless:aws-serverless-java-container-struts:[1.0,)') { - exclude group: 'org.apache.struts', module: 'struts2-core' - exclude group: 'org.apache.logging.log4j', module: 'log4j-api' - exclude group: 'org.apache.logging.log4j', module: 'log4j-to-slf4j' - } - implementation ('org.apache.struts:struts2-convention-plugin:6.0.3') { - exclude group: 'org.apache.struts', module: 'struts2-core' - } - implementation ('org.apache.struts:struts2-rest-plugin:6.0.3') { - exclude group: 'org.apache.struts', module: 'struts2-core' - } - implementation ('org.apache.struts:struts2-bean-validation-plugin:6.0.3') { - exclude group: 'org.apache.struts', module: 'struts2-core' - } - implementation ('com.jgeppert.struts2:struts2-aws-lambda-support-plugin:1.4.2') { - exclude group: 'org.apache.struts', module: 'struts2-core' - } - implementation ('org.apache.struts:struts2-core:6.0.3') { - exclude group: 'org.apache.logging.log4j', module: 'log4j-api' - } - implementation ('org.hibernate.validator:hibernate-validator:6.1.7.Final') - implementation ('com.fasterxml.jackson.core:jackson-databind:2.14.0') - implementation ('com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0') - implementation ('org.apache.logging.log4j:log4j-core:2.19.0') - implementation ('org.apache.logging.log4j:log4j-api:2.19.0') - implementation ('org.apache.logging.log4j:log4j-slf4j-impl:2.19.0') - implementation ('com.amazonaws:aws-lambda-java-log4j2:1.5.1') - - testImplementation('junit:junit:4.13.2') - testImplementation('org.apache.struts:struts2-junit-plugin:6.0.3') { - exclude group: 'org.apache.struts', module: 'struts2-core' - } -} - -task buildZip(type: Zip) { - from compileJava - from processResources - into('lib') { - from(configurations.compileClasspath) - } -} - -build.dependsOn buildZip diff --git a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-struts-archetype/src/main/resources/archetype-resources/pom.xml deleted file mode 100644 index ef32c9b28..000000000 --- a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/pom.xml +++ /dev/null @@ -1,166 +0,0 @@ - - - 4.0.0 - - \${groupId} - \${artifactId} - \${version} - jar - - Serverless Struts API - https://github.com/awslabs/aws-serverless-java-container - - - 1.8 - 1.8 - 6.0.3 - 2.14.0 - 4.13.2 - 2.19.0 - - - - - struts-staging - https://repository.apache.org/content/repositories/staging/ - - - - - - com.amazonaws.serverless - aws-serverless-java-container-struts - ${project.version} - - - - com.amazonaws - aws-lambda-java-core - 1.2.1 - - - - org.apache.struts - struts2-convention-plugin - \${struts.version} - - - - org.apache.struts - struts2-rest-plugin - \${struts.version} - - - - org.apache.struts - struts2-bean-validation-plugin - \${struts.version} - - - - org.apache.struts - struts2-junit-plugin - \${struts.version} - test - - - - - com.jgeppert.struts2 - struts2-aws-lambda-support-plugin - 1.4.2 - - - - - org.hibernate.validator - hibernate-validator - 6.1.7.Final - - - - com.fasterxml.jackson.core - jackson-core - \${jackson.version} - - - com.fasterxml.jackson.core - jackson-annotations - \${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - \${jackson.version} - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - \${jackson.version} - - - - org.apache.logging.log4j - log4j-core - \${log4j.version} - - - - org.apache.logging.log4j - log4j-api - \${log4j.version} - - - - com.amazonaws - aws-lambda-java-log4j2 - 1.5.1 - - - - javax.el - javax.el-api - 2.2.5 - test - - - org.glassfish.web - javax.el - 2.2.6 - test - - - - junit - junit - \${junit.version} - test - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.3.0 - - - src/main/assembly/dist.xml - - - - - lambda - package - - single - - - - - - - diff --git a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/assembly/dist.xml b/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/assembly/dist.xml deleted file mode 100644 index 0466b85c5..000000000 --- a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/assembly/dist.xml +++ /dev/null @@ -1,31 +0,0 @@ - - lambda - - zip - - false - - - lib - false - - - - - ${basedir}/src/main/resources - - - * - - - - ${project.build.directory}/classes - - - **/*.class - - - - \ No newline at end of file diff --git a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/java/actions/PingController.java b/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/java/actions/PingController.java deleted file mode 100644 index f3763f4de..000000000 --- a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/java/actions/PingController.java +++ /dev/null @@ -1,70 +0,0 @@ -package ${groupId}.actions; - - -import com.opensymphony.xwork2.ModelDriven; -import org.apache.struts2.rest.DefaultHttpHeaders; -import org.apache.struts2.rest.HttpHeaders; -import org.apache.struts2.rest.RestActionSupport; - - -import java.util.Collection; -import java.util.UUID; - - -public class PingController extends RestActionSupport implements ModelDriven { - - private String model = new String(); - private String id; - private Collection list = null; - - - // GET /ping/1 - public HttpHeaders show() { - return new DefaultHttpHeaders("show"); - } - - // GET /ping - public HttpHeaders index() { - this.model = "Hello, World!"; - return new DefaultHttpHeaders("index") - .disableCaching(); - } - - // POST /ping - public HttpHeaders create() { - this.model = UUID.randomUUID().toString(); - return new DefaultHttpHeaders("success") - .setLocationId(model); - - } - - // PUT /ping/1 - public String update() { - //TODO: UPDATE LOGIC - return SUCCESS; - } - - // DELETE /ping/1 - public String destroy() { - //TODO: DELETE LOGIC - return SUCCESS; - } - - public void setId(String id) { - if (id != null) { - this.model = "New model instance"; - } - this.id = id; - } - - public Object getModel() { - if (list != null) { - return list; - } else { - if (model == null) { - model = "Pong"; - } - return model; - } - } -} diff --git a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties b/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties deleted file mode 100644 index ec1cb9792..000000000 --- a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -# Reduce logging level to make sure the application works with SAM local -# https://github.com/awslabs/aws-serverless-java-container/issues/134 -logging.level.root=WARN \ No newline at end of file diff --git a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/resources/log4j2.xml b/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/resources/log4j2.xml deleted file mode 100644 index 55ed0d21c..000000000 --- a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/resources/log4j2.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n - - - - - - - - - - - \ No newline at end of file diff --git a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/resources/struts.xml b/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/resources/struts.xml deleted file mode 100644 index f6975ffd0..000000000 --- a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/main/resources/struts.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java b/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java deleted file mode 100644 index 12fc9fc07..000000000 --- a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package ${groupId}; - - -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.AwsProxyResponse; -import com.amazonaws.services.lambda.runtime.Context; - -import com.amazonaws.serverless.proxy.struts.StrutsLambdaHandler; - -import org.junit.BeforeClass; -import org.junit.Test; - -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import static org.junit.Assert.*; - - -public class StreamLambdaHandlerTest { - - private static StrutsLambdaHandler handler; - private static Context lambdaContext; - - @BeforeClass - public static void setUp() { - handler = new StrutsLambdaHandler(); - lambdaContext = new MockLambdaContext(); - } - - @Test - public void ping_streamRequest_respondsWithHello() { - InputStream requestStream = new AwsProxyRequestBuilder("/ping", HttpMethod.GET) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) - .buildStream(); - ByteArrayOutputStream responseStream = new ByteArrayOutputStream(); - - handle(requestStream, responseStream); - - AwsProxyResponse response = readResponse(responseStream); - assertNotNull(response); - assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode()); - - assertFalse(response.isBase64Encoded()); - - assertTrue(response.getBody().contains("Hello, World!")); - - assertTrue(response.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); - assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON)); - } - - @Test - public void invalidResource_streamRequest_responds404() { - InputStream requestStream = new AwsProxyRequestBuilder("/pong", HttpMethod.GET) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) - .buildStream(); - ByteArrayOutputStream responseStream = new ByteArrayOutputStream(); - - handle(requestStream, responseStream); - - AwsProxyResponse response = readResponse(responseStream); - assertNotNull(response); - assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatusCode()); - } - - private void handle(InputStream is, ByteArrayOutputStream os) { - try { - handler.handleRequest(is, os, lambdaContext); - } catch (IOException e) { - e.printStackTrace(); - fail(e.getMessage()); - } - } - - private AwsProxyResponse readResponse(ByteArrayOutputStream responseStream) { - try { - return LambdaContainerHandler.getObjectMapper().readValue(responseStream.toByteArray(), AwsProxyResponse.class); - } catch (IOException e) { - e.printStackTrace(); - fail("Error while parsing response: " + e.getMessage()); - } - return null; - } -} diff --git a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/template.yml b/aws-serverless-struts-archetype/src/main/resources/archetype-resources/template.yml deleted file mode 100644 index fe446dedd..000000000 --- a/aws-serverless-struts-archetype/src/main/resources/archetype-resources/template.yml +++ /dev/null @@ -1,52 +0,0 @@ -#set($resourceName = $artifactId) -#macro(replaceChar $originalName, $char) - #if($originalName.contains($char)) - #set($tokens = $originalName.split($char)) - #set($newResourceName = "") - #foreach($token in $tokens) - #set($newResourceName = $newResourceName + $token.substring(0,1).toUpperCase() + $token.substring(1).toLowerCase()) - #end - ${newResourceName} - #else - #set($newResourceName = $originalName.substring(0,1).toUpperCase() + $originalName.substring(1)) - ${newResourceName} - #end -#end -#set($resourceName = "#replaceChar($resourceName, '-')") -#set($resourceName = "#replaceChar($resourceName, '.')") -#set($resourceName = $resourceName.replaceAll("\n", "").trim()) -#macro(regionVar) - AWS::Region -#end -#set($awsRegion = "#regionVar()") -#set($awsRegion = $awsRegion.replaceAll("\n", "").trim()) -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: AWS Serverless Apache Struts API - ${groupId}::${artifactId} -Globals: - Api: - EndpointConfiguration: REGIONAL - -Resources: - ${resourceName}Function: - Type: AWS::Serverless::Function - Properties: - Handler: com.amazonaws.serverless.proxy.struts.StrutsLambdaHandler::handleRequest - Runtime: java11 - CodeUri: . - MemorySize: 512 - Policies: AWSLambdaBasicExecutionRole - Timeout: 30 - Events: - ProxyResource: - Type: Api - Properties: - Path: /{proxy+} - Method: any - -Outputs: - ${resourceName}Api: - Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${${awsRegion}}.amazonaws.com/Prod/ping' - Export: - Name: ${resourceName}Api diff --git a/aws-serverless-struts-archetype/src/test/resources/projects/base/archetype.properties b/aws-serverless-struts-archetype/src/test/resources/projects/base/archetype.properties deleted file mode 100644 index 80acd0f43..000000000 --- a/aws-serverless-struts-archetype/src/test/resources/projects/base/archetype.properties +++ /dev/null @@ -1,3 +0,0 @@ -groupId=test.service -artifactId=struts-archetype-test -version=1.0-SNAPSHOT diff --git a/aws-serverless-struts-archetype/src/test/resources/projects/base/goal.txt b/aws-serverless-struts-archetype/src/test/resources/projects/base/goal.txt deleted file mode 100644 index 597acc768..000000000 --- a/aws-serverless-struts-archetype/src/test/resources/projects/base/goal.txt +++ /dev/null @@ -1 +0,0 @@ -package \ No newline at end of file diff --git a/gha_build.sh b/gha_build.sh index 9d0001ca4..247e09105 100755 --- a/gha_build.sh +++ b/gha_build.sh @@ -31,7 +31,7 @@ function archetype { cd ${WORKING_DIR}/${ARCHETYPE_NAME} && mvn -q clean install ARCHETYPE_TEST_DIR=${WORKING_DIR}/$1_archetype_test mkdir -p ${ARCHETYPE_TEST_DIR} - cd ${ARCHETYPE_TEST_DIR} && mvn archetype:generate -DgroupId=my.service -DartifactId=${PROJ_NAME} -Dversion=1.0-SNAPSHOT \ + cd ${ARCHETYPE_TEST_DIR} && mvn archetype:generate -DgroupId=my.service -DartifactId=${PROJ_NAME} -Dversion=2.0-SNAPSHOT \ -DarchetypeGroupId=com.amazonaws.serverless.archetypes \ -DarchetypeArtifactId=${ARCHETYPE_NAME} \ -DarchetypeCatalog=local \ @@ -51,10 +51,6 @@ function archetype { if [[ "$?" -ne 0 ]]; then exit 1 fi - cd ${ARCHETYPE_TEST_DIR}/${PROJ_NAME} && ./gradlew wrapper --gradle-version 5.0 - if [[ "$?" -ne 0 ]]; then - exit 1 - fi cd ${ARCHETYPE_TEST_DIR}/${PROJ_NAME} && ./gradlew -q clean build if [[ "$?" -ne 0 ]]; then exit 1 @@ -62,24 +58,23 @@ function archetype { } function sample { - # force to pet store for now. In the future we may loop over all samples - SAMPLE_FOLDER=${WORKING_DIR}/samples/$1/pet-store - cd ${SAMPLE_FOLDER} && mvn -q clean package - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - cd ${SAMPLE_FOLDER} && gradle -q wrapper - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - cd ${SAMPLE_FOLDER} && ./gradlew wrapper --gradle-version 5.0 - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - cd ${SAMPLE_FOLDER} && ./gradlew -q clean build - if [[ "$?" -ne 0 ]]; then - exit 1 - fi + for d in ${WORKING_DIR}/samples/$1/*/ ; do + SAMPLE_FOLDER="$d" + cd ${SAMPLE_FOLDER} && mvn -q clean package + if [[ "$?" -ne 0 ]]; then + exit 1 + fi + if [ -n "$(find ${SAMPLE_FOLDER} -name '*gradle*' | head -1)" ]; then + cd ${SAMPLE_FOLDER} && gradle -q wrapper + if [[ "$?" -ne 0 ]]; then + exit 1 + fi + cd ${SAMPLE_FOLDER} && ./gradlew -q clean build + if [[ "$?" -ne 0 ]]; then + exit 1 + fi + fi + done } # set up the master pom otherwise we won't be able to find new dependencies diff --git a/owasp-suppression.xml b/owasp-suppression.xml index 83fde5815..0f16cbd34 100644 --- a/owasp-suppression.xml +++ b/owasp-suppression.xml @@ -20,16 +20,8 @@ - - cpe:/a:amazon_aws_project:amazon_aws:7.x-1.2::~~~drupal~~ - - - - cpe:/a:restful_web_services_project:restful_web_services:7.x-2.1::~~~drupal~~ - - - - ^pkg:maven/org\.springframework/spring.*$ - CVE-2016-1000027 + + ^pkg:maven/com.fasterxml.jackson.core/jackson-databind@.*$ + CVE-2023-35116 \ No newline at end of file diff --git a/pom.xml b/pom.xml index d5ff104b2..ebc634fbb 100644 --- a/pom.xml +++ b/pom.xml @@ -4,13 +4,13 @@ com.amazonaws.serverless aws-serverless-java-container pom - 1.10-SNAPSHOT + 2.1.5-SNAPSHOT AWS Serverless Java container A Java framework to run Spring, Spring Boot, Jersey, Spark, and Struts applications inside AWS Lambda - https://github.com/awslabs/aws-serverless-java-container + https://github.com/aws/serverless-java-container GitHub Issues - https://github.com/awslabs/aws-serverless-java-container/issues + https://github.com/aws/serverless-java-container/issues @@ -27,22 +27,18 @@ aws-serverless-java-container-core aws-serverless-java-container-jersey - aws-serverless-java-container-spark aws-serverless-java-container-spring - aws-serverless-java-container-struts - aws-serverless-java-container-springboot2 - aws-serverless-struts-archetype + aws-serverless-java-container-springboot3 aws-serverless-jersey-archetype - aws-serverless-spark-archetype aws-serverless-spring-archetype - aws-serverless-springboot2-archetype + aws-serverless-springboot3-archetype - https://github.com/awslabs/aws-serverless-java-container - scm:git:https://github.com/awslabs/aws-serverless-java-container.git + https://github.com/aws/serverless-java-container + scm:git:https://github.com/aws/serverless-java-container.git HEAD - + @@ -81,29 +77,38 @@ 0.7 - 7.3.0 - 2.14.0 - 2.0.3 + 12.1.1 + 2.19.1 + 2.0.17 + 5.12.2 + 5.19.0 + 1.3 UTF-8 - - junit - junit - 4.13.2 + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params test - org.slf4j slf4j-api ${slf4j.version} - org.slf4j slf4j-simple @@ -111,36 +116,69 @@ test - + + org.apache.httpcomponents.client5 + httpclient5 + 5.5 + test + + org.mockito - mockito-all - 1.10.19 + mockito-core + ${mockito.version} + test + + + + org.hamcrest + hamcrest-all + ${hamcrest.version} test - - com.google.code.findbugs - annotations - 3.0.1 + com.github.spotbugs + spotbugs-annotations + 4.9.3 provided + + + + org.junit + junit-bom + ${junit.version} + import + pom + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + org.apache.maven.plugins maven-enforcer-plugin - 3.0.0-M1 + 3.5.0 enforce + + 3.6 + @@ -152,6 +190,7 @@ org.apache.maven.plugins maven-compiler-plugin + 3.14.0 1.8 1.8 @@ -162,23 +201,55 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.3.0 + 3.11.2 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.3 + + + org.apache.maven.plugins + maven-clean-plugin + 3.4.1 + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.4 + + + org.apache.maven.plugins + maven-install-plugin + 3.1.4 - org.apache.maven.plugins maven-release-plugin + 3.1.1 + serverless-java-container-release clean verify install true chore: release - + true com.github.spotbugs spotbugs-maven-plugin - 4.7.3.0 + 4.9.3.0 - - io.symphonia - lambda-logging - 1.0.3 - - @@ -98,7 +85,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 false @@ -125,7 +112,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.4.2 default-jar @@ -136,7 +123,7 @@ org.apache.maven.plugins maven-install-plugin - 3.0.0-M1 + 3.1.4 true @@ -145,7 +132,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.2.0 + 3.8.1 copy-dependencies @@ -163,7 +150,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.3.0 + 3.7.1 zip-assembly diff --git a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/PetsResource.java b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/PetsResource.java index 2d92510d7..c8ad7ebdd 100644 --- a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/PetsResource.java +++ b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/PetsResource.java @@ -15,9 +15,9 @@ import com.amazonaws.serverless.sample.jersey.model.Pet; import com.amazonaws.serverless.sample.jersey.model.PetData; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.util.UUID; @Path("/pets") diff --git a/samples/jersey/pet-store/template.yml b/samples/jersey/pet-store/template.yml index f715064c4..f16d04a7a 100644 --- a/samples/jersey/pet-store/template.yml +++ b/samples/jersey/pet-store/template.yml @@ -12,7 +12,7 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: com.amazonaws.serverless.sample.jersey.StreamLambdaHandler::handleRequest - Runtime: java11 + Runtime: java21 CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole diff --git a/samples/micronaut/pet-store/Dockerfile b/samples/micronaut/pet-store/Dockerfile deleted file mode 100644 index 6bb80136e..000000000 --- a/samples/micronaut/pet-store/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM amazonlinux:2017.03.1.20170812 as graalvm -# install graal to amazon linux. -ENV LANG=en_US.UTF-8 - -RUN yum install -y gcc gcc-c++ libc6-dev zlib1g-dev curl bash zlib zlib-devel zip \ -# && yum install -y libcxx libcxx-devel llvm-toolset-7 \ - && rm -rf /var/cache/yum - - -ENV GRAAL_VERSION 19.2.0.1 -ENV GRAAL_FILENAME graalvm-ce-linux-amd64-${GRAAL_VERSION}.tar.gz - -RUN curl -4 -L https://github.com/oracle/graal/releases/download/vm-${GRAAL_VERSION}/graalvm-ce-linux-amd64-${GRAAL_VERSION}.tar.gz -o /tmp/${GRAAL_FILENAME} - -RUN tar -zxvf /tmp/${GRAAL_FILENAME} -C /tmp \ - && mv /tmp/graalvm-ce-${GRAAL_VERSION} /usr/lib/graalvm - -RUN rm -rf /tmp/* -RUN /usr/lib/graalvm/bin/gu install native-image -ADD scripts/graalvm-build.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/graalvm-build.sh -VOLUME ["/func"] -WORKDIR /func -ENTRYPOINT ["/usr/local/bin/graalvm-build.sh"] \ No newline at end of file diff --git a/samples/micronaut/pet-store/README.md b/samples/micronaut/pet-store/README.md deleted file mode 100644 index fc5793a59..000000000 --- a/samples/micronaut/pet-store/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# Micronaut Native Pet store example - -The [Micronaut framework](https://micronaut.io/) is compatible with Spring's annotations and makes it easy to use [GraalVM](https://www.graalvm.org/) to build application images into native binaries. Further, Micronaut includes builtin support for AWS Lambda. - -This demo application shows how to use Micronaut to compile our standard pet store example, using Spring annotations, into a native binary with GraalVM and execute it in AWS Lambda. To run this demo, you will need to have [Gradle](https://gradle.org/) installed as well as [Docker](https://www.docker.com/) to run the GraalVM build. - -With all the pre-requisites installed including: - -* JDK 8 or above -* Gradle 5.6.x - -You should be able to build a native image of the application by running the `docker-build.sh` from the repository's root. - -```bash -$ ./docker-build.sh -``` - -The build process will perform the following tasks: -1. Use gradle to compile the application -2. Create a `native-image` folder in the current directory -3. Check if a Docker image called `graalvm-lambda-build` exists on the local machine. If not, create it using the `Dockerfile` in the root of the repository -4. Run the docker image to execute the GraalVM build process - -The output of the build is an executable file called `server` and a deployable zip called `function.zip` in the `native-image` folder: - -```bash -$ ls -lh native-image/ -total 75M --rwxr-xr-x 1 user user 36 Oct 1 16:01 bootstrap --rw-r--r-- 1 user user 18M Oct 1 16:01 function.zip --rwxr-xr-x 1 user user 57M Oct 1 16:01 server -``` - -To run the lambda locally, you can utilize the SAM cli by running `./sam-local.sh`. This should start up the listeners in the `PetsController`, and you can test locally with your preferred http client. - -For example, to test the GET /pets endpoint via curl: -```bash -curl localhost:3000/pets -``` - -You should see JSON output of pets. - -To deploy the application to AWS Lambda you can use the pre-configured `sam-native.yaml` file included in the repo. Using the AWS or SAM CLI, run the following commands: - -```bash -$ aws cloudformation package --template-file sam-native.yaml \ - --output-template-file packaged-sam.yaml \ - --s3-bucket -Uploading to d3e2617f3c8c2e8ca78e35a0dc856884 18553505 / 18553505.0 (100.00%) -Successfully packaged artifacts and wrote output template to file packaged-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /workspace/graal-spring-demo/packaged-sam.yaml --stack-name - - -$ aws cloudformation deploy --template-file ./packaged-sam.yaml --stack-name MicronautGraalVmDemo --capabilities CAPABILITY_IAM - -Waiting for changeset to be created.. -Waiting for stack create/update to complete -Successfully created/updated stack - MicronautGraalVmDemo -``` - -You can use the AWS CLI to get the API endpoint generated by the deployment process: - -```bash -aws cloudformation describe-stacks --stack-name MicronautGraalVmDemo --query Stacks[0].Outputs[0].OutputValue -"https://xxxxxxxxxx.execute-api.xx-xxxx-1.amazonaws.com/Prod/pets" -``` - -Make a test request to the API endpoint using curl or your preferred http client. - -For example, to check the GET /pets endpoint via curl: -```bash -curl https://xxxxxxxxxx.execute-api.xx-xxxx-1.amazonaws.com/Prod/pets -``` diff --git a/samples/micronaut/pet-store/build.gradle b/samples/micronaut/pet-store/build.gradle deleted file mode 100644 index 6282d2d2c..000000000 --- a/samples/micronaut/pet-store/build.gradle +++ /dev/null @@ -1,77 +0,0 @@ -plugins { - id "java" - 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.12.RELEASE" - id "io.spring.dependency-management" version "1.0.6.RELEASE" -} - -version "0.1" -group "graal.spring.demo" - -repositories { - mavenCentral() - maven { url "https://jcenter.bintray.com" } -} - -configurations { - // for dependencies that are needed for development only - developmentOnly -} - -dependencies { - annotationProcessor platform("io.micronaut:micronaut-bom:$micronautVersion") - annotationProcessor "io.micronaut:micronaut-graal" - annotationProcessor "io.micronaut:micronaut-inject-java" - annotationProcessor "io.micronaut:micronaut-validation" - annotationProcessor "io.micronaut.spring:micronaut-spring-boot" - annotationProcessor "io.micronaut.spring:micronaut-spring-boot-annotation" - annotationProcessor "io.micronaut.spring:micronaut-spring-web-annotation" - testAnnotationProcessor "io.micronaut.spring:micronaut-spring-web-annotation" - - compileOnly "com.oracle.substratevm:svm" - implementation platform("io.micronaut:micronaut-bom:$micronautVersion") - implementation "io.micronaut:micronaut-http-client" - implementation "io.micronaut:micronaut-inject" - implementation "io.micronaut:micronaut-validation" - implementation "io.micronaut:micronaut-runtime" - implementation("io.micronaut.aws:micronaut-function-aws-custom-runtime:1.3.2") { - exclude group: "com.fasterxml.jackson.module", module: "jackson-module-afterburner" - } - implementation("io.micronaut.aws:micronaut-function-aws-api-proxy:1.3.2") { - exclude group: "com.fasterxml.jackson.module", module: "jackson-module-afterburner" - } - developmentOnly "io.micronaut:micronaut-http-server-netty" - runtimeOnly "ch.qos.logback:logback-classic:1.2.3" - testAnnotationProcessor platform("io.micronaut:micronaut-bom:$micronautVersion") - testAnnotationProcessor "io.micronaut:micronaut-inject-java" - testImplementation platform("io.micronaut:micronaut-bom:$micronautVersion") - testImplementation "org.junit.jupiter:junit-jupiter-api" - testImplementation "io.micronaut.test:micronaut-test-junit5" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine" - - // spring support - implementation("org.springframework.boot:spring-boot-starter-web") - runtime("io.micronaut.spring:micronaut-spring-boot:1.0.1") - runtime("io.micronaut.spring:micronaut-spring-web:1.0.1") -} - -test.classpath += configurations.developmentOnly - -mainClassName = "graal.spring.demo.Application" -// use JUnit 5 platform -test { - useJUnitPlatform() -} - -shadowJar { - mergeServiceFiles() -} - -run.classpath += configurations.developmentOnly -run.jvmArgs('-noverify', '-XX:TieredStopAtLevel=1', '-Dcom.sun.management.jmxremote') -tasks.withType(JavaCompile){ - options.encoding = "UTF-8" - options.compilerArgs.add('-parameters') -} diff --git a/samples/micronaut/pet-store/docker-build.sh b/samples/micronaut/pet-store/docker-build.sh deleted file mode 100755 index 41a644cc0..000000000 --- a/samples/micronaut/pet-store/docker-build.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -DOCKER_IMAGE_NAME=graalvm-lambda-build - -if [[ -d "${PWD}/native-image" ]]; then - rm -rf native-image/* -fi - -gradle clean build --info - -if [[ $? -ne 0 ]]; then - echo "Gradle build failed" - exit 1 -fi - -mkdir -p ${PWD}/native-image - -if [[ "$(docker images -q ${DOCKER_IMAGE_NAME} 2> /dev/null)" == "" ]]; then - docker build . -t ${DOCKER_IMAGE_NAME} -fi - -docker run --rm -v ${PWD}:/func ${DOCKER_IMAGE_NAME} - -if [[ -f "${PWD}/native-image/function.zip" ]]; then - echo "The function is ready to deploy in the ./native-image/function.zip file. Use the sam-native.yaml template to set up your Lambda function and API" -fi diff --git a/samples/micronaut/pet-store/gradle.properties b/samples/micronaut/pet-store/gradle.properties deleted file mode 100644 index 04c818602..000000000 --- a/samples/micronaut/pet-store/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -micronautVersion=1.2.3 \ No newline at end of file diff --git a/samples/micronaut/pet-store/micronaut-cli.yml b/samples/micronaut/pet-store/micronaut-cli.yml deleted file mode 100644 index 262c35c06..000000000 --- a/samples/micronaut/pet-store/micronaut-cli.yml +++ /dev/null @@ -1,5 +0,0 @@ -profile: service -defaultPackage: com.amazonaws.micronaut.demo ---- -testFramework: junit -sourceLanguage: java \ No newline at end of file diff --git a/samples/micronaut/pet-store/sam-local.sh b/samples/micronaut/pet-store/sam-local.sh deleted file mode 100755 index 0c82b8511..000000000 --- a/samples/micronaut/pet-store/sam-local.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -./docker-build.sh -sam local start-api -t sam-native.yaml \ No newline at end of file diff --git a/samples/micronaut/pet-store/sam-native.yaml b/samples/micronaut/pet-store/sam-native.yaml deleted file mode 100644 index 76a08d1ec..000000000 --- a/samples/micronaut/pet-store/sam-native.yaml +++ /dev/null @@ -1,30 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: AWS Serverless Micronaut API - graal.spring.demo::graal-spring-demo -Globals: - Api: - EndpointConfiguration: REGIONAL -Resources: - GraalVMApiService: - Type: AWS::Serverless::Function - Properties: - Handler: not.used.in.provided.runtime - Runtime: provided - CodeUri: native-image/function.zip - MemorySize: 512 - Policies: AWSLambdaBasicExecutionRole - Tracing: Active - Timeout: 15 - Events: - GetResource: - Type: Api - Properties: - Path: /{proxy+} - Method: any - -Outputs: - MicronautNativeServiceApi: - Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' - Export: - Name: MicronautNativeServiceApi diff --git a/samples/micronaut/pet-store/scripts/bootstrap b/samples/micronaut/pet-store/scripts/bootstrap deleted file mode 100755 index f10361a21..000000000 --- a/samples/micronaut/pet-store/scripts/bootstrap +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -set -euo pipefail -./server \ No newline at end of file diff --git a/samples/micronaut/pet-store/scripts/graalvm-build.sh b/samples/micronaut/pet-store/scripts/graalvm-build.sh deleted file mode 100755 index 44b8b2603..000000000 --- a/samples/micronaut/pet-store/scripts/graalvm-build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -echo "Startin GraalVM build" -/usr/lib/graalvm/bin/native-image -H:+TraceClassInitialization --initialize-at-build-time=reactor.core.publisher.Mono --initialize-at-build-time=reactor.core.publisher.Flux --no-fallback --no-server -cp /func/build/libs/pet-store-*-all.jar -rm -rf /func/native-image/* - -chmod 755 /func/server -mv /func/server /func/native-image/server -cp /func/scripts/bootstrap /func/native-image/bootstrap -cd /func/native-image && zip -j function.zip bootstrap server diff --git a/samples/micronaut/pet-store/settings.gradle b/samples/micronaut/pet-store/settings.gradle deleted file mode 100644 index a6e417eb2..000000000 --- a/samples/micronaut/pet-store/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name="pet-store" \ No newline at end of file diff --git a/samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/Application.java b/samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/Application.java deleted file mode 100644 index 0340d5c42..000000000 --- a/samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/Application.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.amazonaws.micronaut.demo; - -import org.springframework.boot.autoconfigure.SpringBootApplication; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import io.micronaut.runtime.Micronaut; - -@SpringBootApplication -public class Application { - - public static void main(String[] args) { - Logger rootLogger = (ch.qos.logback.classic.Logger)org.slf4j.LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); - rootLogger.setLevel(Level.TRACE); - Micronaut.run(Application.class); - } -} \ No newline at end of file diff --git a/samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/model/PetData.java b/samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/model/PetData.java deleted file mode 100644 index f7bc03c46..000000000 --- a/samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/model/PetData.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -package com.amazonaws.micronaut.demo.model; - -import org.springframework.stereotype.Component; - -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; - -@Component -public class PetData { - private static List breeds = new ArrayList<>(); - static { - breeds.add("Afghan Hound"); - breeds.add("Beagle"); - breeds.add("Bernese Mountain Dog"); - breeds.add("Bloodhound"); - breeds.add("Dalmatian"); - breeds.add("Jack Russell Terrier"); - breeds.add("Norwegian Elkhound"); - } - - private static List names = new ArrayList<>(); - static { - names.add("Bailey"); - names.add("Bella"); - names.add("Max"); - names.add("Lucy"); - names.add("Charlie"); - names.add("Molly"); - names.add("Buddy"); - names.add("Daisy"); - names.add("Rocky"); - names.add("Maggie"); - names.add("Jake"); - names.add("Sophie"); - names.add("Jack"); - names.add("Sadie"); - names.add("Toby"); - names.add("Chloe"); - names.add("Cody"); - names.add("Bailey"); - names.add("Buster"); - names.add("Lola"); - names.add("Duke"); - names.add("Zoe"); - names.add("Cooper"); - names.add("Abby"); - names.add("Riley"); - names.add("Ginger"); - names.add("Harley"); - names.add("Roxy"); - names.add("Bear"); - names.add("Gracie"); - names.add("Tucker"); - names.add("Coco"); - names.add("Murphy"); - names.add("Sasha"); - names.add("Lucky"); - names.add("Lily"); - names.add("Oliver"); - names.add("Angel"); - names.add("Sam"); - names.add("Princess"); - names.add("Oscar"); - names.add("Emma"); - names.add("Teddy"); - names.add("Annie"); - names.add("Winston"); - names.add("Rosie"); - } - - public List getBreeds() { - return breeds; - } - - public List getNames() { - return names; - } - - public String getRandomBreed() { - return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1)); - } - - public String getRandomName() { - return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1)); - } - - public Date getRandomDoB() { - GregorianCalendar gc = new GregorianCalendar(); - - int year = ThreadLocalRandom.current().nextInt( - Calendar.getInstance().get(Calendar.YEAR) - 15, - Calendar.getInstance().get(Calendar.YEAR) - ); - - gc.set(Calendar.YEAR, year); - - int dayOfYear = ThreadLocalRandom.current().nextInt(1, gc.getActualMaximum(Calendar.DAY_OF_YEAR)); - - gc.set(Calendar.DAY_OF_YEAR, dayOfYear); - return gc.getTime(); - } -} diff --git a/samples/micronaut/pet-store/src/main/resources/META-INF/native-image/graal.spring.demo/graal-spring-demo-application/native-image.properties b/samples/micronaut/pet-store/src/main/resources/META-INF/native-image/graal.spring.demo/graal-spring-demo-application/native-image.properties deleted file mode 100644 index fd9f904ee..000000000 --- a/samples/micronaut/pet-store/src/main/resources/META-INF/native-image/graal.spring.demo/graal-spring-demo-application/native-image.properties +++ /dev/null @@ -1,4 +0,0 @@ -Args = -H:IncludeResources=application.yml|log4j2.xml \ - -H:Name=server \ - -H:-AllowVMInspection \ - -H:Class=io.micronaut.function.aws.runtime.MicronautLambdaRuntime \ No newline at end of file diff --git a/samples/micronaut/pet-store/src/main/resources/application.yml b/samples/micronaut/pet-store/src/main/resources/application.yml deleted file mode 100644 index c16e60c1d..000000000 --- a/samples/micronaut/pet-store/src/main/resources/application.yml +++ /dev/null @@ -1,6 +0,0 @@ -micronaut: - application: - name: pet-store -jackson: - serialization: - indentOutput: true \ No newline at end of file diff --git a/samples/micronaut/pet-store/src/main/resources/logback.xml b/samples/micronaut/pet-store/src/main/resources/logback.xml deleted file mode 100644 index 6010eb523..000000000 --- a/samples/micronaut/pet-store/src/main/resources/logback.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - true - - - %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n - - - - - - - diff --git a/samples/quarkus/pet-store/README.md b/samples/quarkus/pet-store/README.md deleted file mode 100644 index 3ff1371d7..000000000 --- a/samples/quarkus/pet-store/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# Quarkus Native Pet store example - -The [Quarkus framework](https://quarkus.io/) is compatible with Spring's annotations and makes it easy to use [GraalVM](https://www.graalvm.org/) to build application images into native binaries. Further, Micronaut includes builtin support for AWS Lambda. - -This demo application shows how to use Quarkus to compile our standard pet store example, using Spring annotations, into a native binary with GraalVM and execute it in AWS Lambda. To run this demo, you will need to have [Maven](https://maven.apache.org/) installed as well as [Docker](https://www.docker.com/) to build GraalVM native image. - -With all the pre-requisites installed including: - -* JDK 8 or above -* Maven 3.5.x - -You should be able to build a native image of the application by running mvn from the repository's root. - -```bash -$ mvn clean install -Pnative -``` - -The output of the build is a deployable zip called `function.zip` in the `target` folder. - -To run the lambda locally, you can utilize the SAM cli. This should start up the listeners in the `PetsController`, and you can test locally with your preferred http client. - -```bash -sam local start-api -t sam.native.yaml -``` - -For example, to test the GET /pets endpoint via curl: -```bash -curl localhost:3000/pets -``` - -You should see JSON output of pets. - -To deploy the application to AWS Lambda you can use the pre-configured `sam-native.yaml` file included in the repo. Using the AWS or SAM CLI, run the following commands: - -```bash -sam deploy -g -t sam.native.yaml -``` - -You should see the stack deployed successfully: - -```bash -Stack quarkus-sample-pet-store outputs: -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -OutputKey-Description OutputValue -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -PetStoreNativeApi - URL for application https://xxxxxxxxxx.execute-api.xx-xxxx-1.amazonaws.com/Prod/ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -Successfully created/updated stack - quarkus-sample-pet-store in xx-xxxx-1 - -``` - -Make a test request to the API endpoint using curl or your preferred http client. - -For example, to check the GET /pets endpoint via curl: -```bash -curl https://xxxxxxxxxx.execute-api.xx-xxxx-1.amazonaws.com/Prod/pets -``` - -Finally, there’s an environment variable that must be set for native GraalVM deployments. If you look at sam.native.yaml you’ll see this: - -```bash - Environment: - Variables: - DISABLE_SIGNAL_HANDLERS: true -``` - -This environment variable resolves some incompatibilites between GraalVM and the Amazon Lambda Custom Runtime environment. \ No newline at end of file diff --git a/samples/quarkus/pet-store/pom.xml b/samples/quarkus/pet-store/pom.xml deleted file mode 100644 index 0147be882..000000000 --- a/samples/quarkus/pet-store/pom.xml +++ /dev/null @@ -1,141 +0,0 @@ - - - 4.0.0 - com.amazonaws.serverless.sample - serverless-quarkus-example - 1.0-SNAPSHOT - - 3.8.1 - true - 1.8 - 1.8 - UTF-8 - UTF-8 - 1.0.1.Final - quarkus-universe-bom - io.quarkus - 1.0.1.Final - 2.22.1 - - - - - ${quarkus.platform.group-id} - ${quarkus.platform.artifact-id} - ${quarkus.platform.version} - pom - import - - - - - - io.quarkus - quarkus-resteasy - - - io.quarkus - quarkus-amazon-lambda-http - - - io.quarkus - quarkus-spring-web - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test - - - - - - io.quarkus - quarkus-maven-plugin - ${quarkus-plugin.version} - - - - build - - - - - - maven-compiler-plugin - ${compiler-plugin.version} - - - maven-surefire-plugin - ${surefire-plugin.version} - - - org.jboss.logmanager.LogManager - - - - - - - - native - - - native - - - - - - maven-failsafe-plugin - ${surefire-plugin.version} - - - - integration-test - verify - - - - ${project.build.directory}/${project.build.finalName}-runner - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.1.0 - - - zip-assembly - package - - single - - - function - - src/assembly/zip.xml - - false - false - - - - - - - - native - - - - diff --git a/samples/quarkus/pet-store/sam.native.yaml b/samples/quarkus/pet-store/sam.native.yaml deleted file mode 100644 index 54ca55d07..000000000 --- a/samples/quarkus/pet-store/sam.native.yaml +++ /dev/null @@ -1,36 +0,0 @@ - AWSTemplateFormatVersion: '2010-09-09' - Transform: AWS::Serverless-2016-10-31 - Description: AWS Serverless Quarkus HTTP - com.amazon.quarkus.demo::pet-store - Globals: - Api: - EndpointConfiguration: REGIONAL - BinaryMediaTypes: - - "*/*" - - Resources: - PetStoreNativeFunction: - Type: AWS::Serverless::Function - Properties: - Handler: not.used.in.provided.runtime - Runtime: provided - CodeUri: target/function.zip - MemorySize: 128 - Policies: AWSLambdaBasicExecutionRole - Tracing: Active - Timeout: 15 - Environment: - Variables: - DISABLE_SIGNAL_HANDLERS: true - Events: - GetResource: - Type: Api - Properties: - Path: /{proxy+} - Method: any - - Outputs: - PetStoreNativeApi: - Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/' - Export: - Name: PetStoreNativeApi diff --git a/samples/quarkus/pet-store/src/assembly/zip.xml b/samples/quarkus/pet-store/src/assembly/zip.xml deleted file mode 100644 index ce2cb6421..000000000 --- a/samples/quarkus/pet-store/src/assembly/zip.xml +++ /dev/null @@ -1,17 +0,0 @@ - - lambda-package - - zip - - false - - - ${project.build.directory}${file.separator}${artifactId}-${version}-runner - / - bootstrap - 755 - - - diff --git a/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/PetsController.java b/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/PetsController.java deleted file mode 100644 index 571887850..000000000 --- a/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/PetsController.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.amazonaws.serverless.sample.quarkus; - -import com.amazonaws.serverless.sample.quarkus.model.Pet; -import com.amazonaws.serverless.sample.quarkus.model.PetData; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -import java.util.Optional; -import java.util.UUID; - -@RestController -public class PetsController { - - private PetData petData; - - @Autowired - public PetsController(PetData data) { - petData = data; - } - - @RequestMapping(path = "/pets", method = RequestMethod.POST) - public Pet createPet(@RequestBody Pet newPet) { - if (newPet.getName() == null || newPet.getBreed() == null) { - return null; - } - - Pet dbPet = newPet; - dbPet.setId(UUID.randomUUID().toString()); - return dbPet; - } - - @RequestMapping(path = "/pets", method = RequestMethod.GET) - public Pet[] listPets(@RequestParam("limit") Optional limit) { - int queryLimit = 10; - if (limit.isPresent()) { - queryLimit = limit.get(); - } - - Pet[] outputPets = new Pet[queryLimit]; - - for (int i = 0; i < queryLimit; i++) { - Pet newPet = new Pet(); - newPet.setId(UUID.randomUUID().toString()); - newPet.setName(petData.getRandomName()); - newPet.setBreed(petData.getRandomBreed()); - newPet.setDateOfBirth(petData.getRandomDoB()); - outputPets[i] = newPet; - } - - return outputPets; - } - - @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET) - public Pet getPet(@RequestParam("petId") String petId) { - Pet newPet = new Pet(); - newPet.setId(UUID.randomUUID().toString()); - newPet.setBreed(petData.getRandomBreed()); - newPet.setDateOfBirth(petData.getRandomDoB()); - newPet.setName(petData.getRandomName()); - return newPet; - } - -} diff --git a/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/Pet.java b/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/Pet.java deleted file mode 100644 index 475213362..000000000 --- a/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/Pet.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.amazonaws.serverless.sample.quarkus.model; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.quarkus.runtime.annotations.RegisterForReflection; - -import java.util.Date; - -@RegisterForReflection -public class Pet { - private String id; - private String breed; - private String name; - private Date dateOfBirth; - - @JsonProperty("petId") - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getBreed() { - return breed; - } - - public void setBreed(String breed) { - this.breed = breed; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @JsonFormat(pattern = "YYYY-mm-dd") - public Date getDateOfBirth() { - return dateOfBirth; - } - - public void setDateOfBirth(Date dateOfBirth) { - this.dateOfBirth = dateOfBirth; - } - -} diff --git a/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/PetData.java b/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/PetData.java deleted file mode 100644 index 90b837d0d..000000000 --- a/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/PetData.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.amazonaws.serverless.sample.quarkus.model; - -import org.springframework.stereotype.Component; - -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; - -@Component -public class PetData { - private static List breeds = new ArrayList<>(); - static { - breeds.add("Afghan Hound"); - breeds.add("Beagle"); - breeds.add("Bernese Mountain Dog"); - breeds.add("Bloodhound"); - breeds.add("Dalmatian"); - breeds.add("Jack Russell Terrier"); - breeds.add("Norwegian Elkhound"); - } - - private static List names = new ArrayList<>(); - static { - names.add("Bailey"); - names.add("Bella"); - names.add("Max"); - names.add("Lucy"); - names.add("Charlie"); - names.add("Molly"); - names.add("Buddy"); - names.add("Daisy"); - names.add("Rocky"); - names.add("Maggie"); - names.add("Jake"); - names.add("Sophie"); - names.add("Jack"); - names.add("Sadie"); - names.add("Toby"); - names.add("Chloe"); - names.add("Cody"); - names.add("Bailey"); - names.add("Buster"); - names.add("Lola"); - names.add("Duke"); - names.add("Zoe"); - names.add("Cooper"); - names.add("Abby"); - names.add("Riley"); - names.add("Ginger"); - names.add("Harley"); - names.add("Roxy"); - names.add("Bear"); - names.add("Gracie"); - names.add("Tucker"); - names.add("Coco"); - names.add("Murphy"); - names.add("Sasha"); - names.add("Lucky"); - names.add("Lily"); - names.add("Oliver"); - names.add("Angel"); - names.add("Sam"); - names.add("Princess"); - names.add("Oscar"); - names.add("Emma"); - names.add("Teddy"); - names.add("Annie"); - names.add("Winston"); - names.add("Rosie"); - } - - public List getBreeds() { - return breeds; - } - - public List getNames() { - return names; - } - - public String getRandomBreed() { - return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1)); - } - - public String getRandomName() { - return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1)); - } - - public Date getRandomDoB() { - GregorianCalendar gc = new GregorianCalendar(); - - int year = ThreadLocalRandom.current().nextInt( - Calendar.getInstance().get(Calendar.YEAR) - 15, - Calendar.getInstance().get(Calendar.YEAR) - ); - - gc.set(Calendar.YEAR, year); - - int dayOfYear = ThreadLocalRandom.current().nextInt(1, gc.getActualMaximum(Calendar.DAY_OF_YEAR)); - - gc.set(Calendar.DAY_OF_YEAR, dayOfYear); - return gc.getTime(); - } -} diff --git a/samples/quarkus/pet-store/src/main/resources/application.properties b/samples/quarkus/pet-store/src/main/resources/application.properties deleted file mode 100644 index ee9b09c98..000000000 --- a/samples/quarkus/pet-store/src/main/resources/application.properties +++ /dev/null @@ -1,4 +0,0 @@ -quarkus.native.container-build=true -quarkus.native.container-runtime=docker -quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-native-image:19.2.1 -quarkus.log.level=INFO \ No newline at end of file diff --git a/samples/spark/pet-store/build.gradle b/samples/spark/pet-store/build.gradle deleted file mode 100644 index 053d46ecd..000000000 --- a/samples/spark/pet-store/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -apply plugin: 'java' - -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - implementation ( - 'com.sparkjava:spark-core:2.9.4', - 'com.amazonaws.serverless:aws-serverless-java-container-spark:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.14.0', - 'io.symphonia:lambda-logging:1.0.3' - ) -} - -task buildZip(type: Zip) { - from compileJava - from processResources - into('lib') { - from(configurations.compileClasspath) { - exclude 'jetty-http*' - exclude 'jetty-client*' - exclude 'jetty-webapp*' - exclude 'jetty-xml*' - exclude 'jetty-io*' - exclude 'websocket*' - } - } -} - -build.dependsOn buildZip diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/JsonTransformer.java b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/JsonTransformer.java deleted file mode 100644 index bc1692afd..000000000 --- a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/JsonTransformer.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -package com.amazonaws.serverless.sample.spark; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spark.ResponseTransformer; - -public class JsonTransformer implements ResponseTransformer { - - private ObjectMapper mapper = new ObjectMapper(); - private Logger log = LoggerFactory.getLogger(JsonTransformer.class); - - @Override - public String render(Object model) { - try { - return mapper.writeValueAsString(model); - } catch (JsonProcessingException e) { - log.error("Cannot serialize object", e); - return null; - } - } - -} \ No newline at end of file diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/SparkResources.java b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/SparkResources.java deleted file mode 100644 index 7731a70b1..000000000 --- a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/SparkResources.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.amazonaws.serverless.sample.spark; - - -import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; -import com.amazonaws.serverless.sample.spark.model.Pet; -import com.amazonaws.serverless.sample.spark.model.PetData; - -import javax.ws.rs.core.Response; - -import java.util.UUID; - -import static spark.Spark.before; -import static spark.Spark.get; -import static spark.Spark.post; - - -public class SparkResources { - - public static void defineResources() { - before((request, response) -> response.type("application/json")); - - post("/pets", (req, res) -> { - Pet newPet = LambdaContainerHandler.getObjectMapper().readValue(req.body(), Pet.class); - if (newPet.getName() == null || newPet.getBreed() == null) { - return Response.status(400).entity(new Error("Invalid name or breed")).build(); - } - - Pet dbPet = newPet; - dbPet.setId(UUID.randomUUID().toString()); - - res.status(200); - return dbPet; - }, new JsonTransformer()); - - get("/pets", (req, res) -> { - int limit = 10; - if (req.queryParams("limit") != null) { - limit = Integer.parseInt(req.queryParams("limit")); - } - - Pet[] outputPets = new Pet[limit]; - - for (int i = 0; i < limit; i++) { - Pet newPet = new Pet(); - newPet.setId(UUID.randomUUID().toString()); - newPet.setName(PetData.getRandomName()); - newPet.setBreed(PetData.getRandomBreed()); - newPet.setDateOfBirth(PetData.getRandomDoB()); - outputPets[i] = newPet; - } - - res.status(200); - return outputPets; - }, new JsonTransformer()); - - get("/pets/:petId", (req, res) -> { - Pet newPet = new Pet(); - newPet.setId(UUID.randomUUID().toString()); - newPet.setBreed(PetData.getRandomBreed()); - newPet.setDateOfBirth(PetData.getRandomDoB()); - newPet.setName(PetData.getRandomName()); - res.status(200); - return newPet; - }, new JsonTransformer()); - } -} diff --git a/samples/spring/pet-store/build.gradle b/samples/spring/pet-store/build.gradle index 838afd6ab..d2fecf23e 100644 --- a/samples/spring/pet-store/build.gradle +++ b/samples/spring/pet-store/build.gradle @@ -7,14 +7,14 @@ repositories { dependencies { implementation ( - 'org.springframework:spring-webmvc:5.3.23', - 'org.springframework:spring-context:5.3.23', - 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', - 'org.apache.logging.log4j:log4j-core:2.19.0', - 'org.apache.logging.log4j:log4j-api:2.19.0', - 'org.apache.logging.log4j:log4j-slf4j-impl:2.19.0', - 'com.fasterxml.jackson.core:jackson-databind:2.14.0', - 'com.amazonaws:aws-lambda-java-log4j2:1.5.1', + 'org.springframework:spring-webmvc:6.2.8', + 'org.springframework:spring-context:6.2.8', + 'com.amazonaws.serverless:aws-serverless-java-container-spring:[2.0-SNAPSHOT,)', + 'org.apache.logging.log4j:log4j-core:2.24.3', + 'org.apache.logging.log4j:log4j-api:2.24.3', + 'org.apache.logging.log4j:log4j-slf4j-impl:2.24.3', + 'com.fasterxml.jackson.core:jackson-databind:2.19.1', + 'com.amazonaws:aws-lambda-java-log4j2:1.6.0', ) } diff --git a/samples/spring/pet-store/pom.xml b/samples/spring/pet-store/pom.xml index 2d1a472f3..27a49d371 100644 --- a/samples/spring/pet-store/pom.xml +++ b/samples/spring/pet-store/pom.xml @@ -6,13 +6,13 @@ com.amazonaws.serverless.sample serverless-spring-example - 1.0-SNAPSHOT + 2.0-SNAPSHOT Spring example for the aws-serverless-java-container library Simple pet store written with the Spring framework https://aws.amazon.com/lambda/ - https://github.com/awslabs/aws-serverless-java-container.git + https://github.com/aws/serverless-java-container.git @@ -24,24 +24,17 @@ - 1.8 - 1.8 - 5.3.23 - 4.13.2 - 2.19.0 + 6.2.10 + 2.24.3 + 17 + 17 com.amazonaws.serverless aws-serverless-java-container-spring - [1.6,) - - - - com.amazonaws - aws-lambda-java-core - 1.2.1 + [2.0-SNAPSHOT,) @@ -90,14 +83,7 @@ com.amazonaws aws-lambda-java-log4j2 - 1.5.1 - - - - junit - junit - ${junit.version} - test + 1.6.0 @@ -109,7 +95,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 package @@ -117,10 +103,9 @@ shade - false + implementation="io.github.edwgiz.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer"> @@ -128,9 +113,9 @@ - com.github.edwgiz - maven-shade-plugin.log4j2-cachefile-transformer - 2.8.1 + io.github.edwgiz + log4j-maven-shade-plugin-extensions + 2.20.0 @@ -148,7 +133,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.4.2 default-jar @@ -159,7 +144,7 @@ org.apache.maven.plugins maven-install-plugin - 3.0.0-M1 + 3.1.4 true @@ -168,7 +153,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.2.0 + 3.8.1 copy-dependencies @@ -186,7 +171,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.3.0 + 3.7.1 zip-assembly diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetStoreSpringAppConfig.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetStoreSpringAppConfig.java index 81179933c..5ac5403cd 100644 --- a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetStoreSpringAppConfig.java +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetStoreSpringAppConfig.java @@ -22,8 +22,8 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; @Configuration diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java index 71fb94cea..07675b3fe 100644 --- a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java @@ -10,8 +10,8 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import javax.servlet.DispatcherType; -import javax.servlet.FilterRegistration; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterRegistration; import java.io.IOException; import java.io.InputStream; diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/filter/CognitoIdentityFilter.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/filter/CognitoIdentityFilter.java index d9eefdc92..ec4242b81 100644 --- a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/filter/CognitoIdentityFilter.java +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/filter/CognitoIdentityFilter.java @@ -7,12 +7,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import java.io.IOException; diff --git a/samples/spring/pet-store/template.yml b/samples/spring/pet-store/template.yml index c485cf4c6..34cecbca2 100644 --- a/samples/spring/pet-store/template.yml +++ b/samples/spring/pet-store/template.yml @@ -12,7 +12,7 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: com.amazonaws.serverless.sample.spring.StreamLambdaHandler::handleRequest - Runtime: java11 + Runtime: java21 CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole diff --git a/samples/springboot3/alt-pet-store/README.md b/samples/springboot3/alt-pet-store/README.md new file mode 100644 index 000000000..b91cd42f6 --- /dev/null +++ b/samples/springboot3/alt-pet-store/README.md @@ -0,0 +1,56 @@ +# Serverless Spring Boot 3 example +A basic pet store written with the [Spring Boot 3 framework](https://projects.spring.io/spring-boot/). Unlike older examples, this example is relying on the new +`SpringDelegatingLambdaContainerHandler`, which you simply need to identify as a _handler_ of the Lambda function. The main configuration class identified as `MAIN_CLASS` +environment variable or `Start-Class` or `Main-Class` entry in Manifest file. See provided `template.yml` file for reference. + + +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition. + +## Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) + +## Deployment +In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package +``` +$ sam build +``` + +This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. + +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen + +``` +$ sam deploy --guided +``` + +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL + +``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +--------------------------------------------------------------------------------------------------------- + +$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +``` + +You can also try a complex request passing both path and request parameters to complex endpoint such as this: + + +``` +@RequestMapping(path = "/foo/{gender}/bar/{age}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) +public String complexRequest(@RequestBody String body, + @PathVariable("gender") String gender, + @PathVariable("age") String age, + @RequestParam("name") String name +) +``` +For example. + +``` +curl -d '{"key1":"value1", "key2":"value2"}' -H "Content-Type: application/json" -X POST https://zuhd709386.execute-api.us-east-2.amazonaws.com/foo/male/bar/25?name=Ricky +``` diff --git a/samples/springboot2/pet-store/build.gradle b/samples/springboot3/alt-pet-store/build.gradle similarity index 78% rename from samples/springboot2/pet-store/build.gradle rename to samples/springboot3/alt-pet-store/build.gradle index 22989c9e9..d2c99b907 100644 --- a/samples/springboot2/pet-store/build.gradle +++ b/samples/springboot3/alt-pet-store/build.gradle @@ -1,20 +1,19 @@ apply plugin: 'java' repositories { - jcenter() mavenLocal() mavenCentral() + maven {url "https://repo.spring.io/milestone"} + maven {url "https://repo.spring.io/snapshot"} } dependencies { implementation ( - implementation('org.springframework.boot:spring-boot-starter-web:2.7.5') { + implementation('org.springframework.boot:spring-boot-starter-web:3.5.7') { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' }, - 'com.amazonaws.serverless:aws-serverless-java-container-springboot2:[1.4,)', - 'io.symphonia:lambda-logging:1.0.3' + 'com.amazonaws.serverless:aws-serverless-java-container-springboot3:[2.0-SNAPSHOT,)', ) - testImplementation("junit:junit") } task buildZip(type: Zip) { diff --git a/samples/spark/pet-store/pom.xml b/samples/springboot3/alt-pet-store/pom.xml similarity index 66% rename from samples/spark/pet-store/pom.xml rename to samples/springboot3/alt-pet-store/pom.xml index e25f0f9e4..64cbb083c 100644 --- a/samples/spark/pet-store/pom.xml +++ b/samples/springboot3/alt-pet-store/pom.xml @@ -1,19 +1,20 @@ - 4.0.0 com.amazonaws.serverless.sample - serverless-spark-example - 1.0-SNAPSHOT - Spark example for the aws-serverless-java-container library - Simple pet store written with the Spark framework + petstore-springboot3-example + 2.0-SNAPSHOT + Spring Boot example for the aws-serverless-java-container library + Simple pet store written with the Spring framework and Spring Boot https://aws.amazon.com/lambda/ - - https://github.com/awslabs/aws-serverless-java-container.git - + + org.springframework.boot + spring-boot-starter-parent + 3.5.0 + @@ -24,38 +25,19 @@ - 1.8 - 1.8 - 2.14.0 - 2.9.1 + 17 - com.amazonaws.serverless - aws-serverless-java-container-spark - [1.6,) + org.springframework.boot + spring-boot-starter - - com.sparkjava - spark-core - ${spark.version} - - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - - - io.symphonia - lambda-logging - 1.0.3 + com.amazonaws.serverless + aws-serverless-java-container-springboot3 + [2.2.0-SNAPSHOT,),[2.1.1,) @@ -67,7 +49,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 false @@ -80,15 +62,7 @@ - - org.eclipse.jetty.websocket:* - org.eclipse.jetty:jetty-http - org.eclipse.jetty:jetty-client - org.eclipse.jetty:jetty-webapp - org.eclipse.jetty:jetty-xml - org.eclipse.jetty:jetty-io + org.apache.tomcat.embed:* @@ -109,7 +83,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.4.2 default-jar @@ -120,7 +94,7 @@ org.apache.maven.plugins maven-install-plugin - 3.0.0-M1 + 3.1.4 true @@ -129,7 +103,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.2.0 + 3.8.1 copy-dependencies @@ -147,7 +121,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.3.0 + 3.7.1 zip-assembly @@ -170,4 +144,5 @@ + diff --git a/samples/springboot2/pet-store/src/assembly/bin.xml b/samples/springboot3/alt-pet-store/src/assembly/bin.xml similarity index 100% rename from samples/springboot2/pet-store/src/assembly/bin.xml rename to samples/springboot3/alt-pet-store/src/assembly/bin.xml diff --git a/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java new file mode 100644 index 000000000..428d67267 --- /dev/null +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java @@ -0,0 +1,51 @@ +package com.amazonaws.serverless.sample.springboot3; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import com.amazonaws.serverless.sample.springboot3.controller.PetsController; +import com.amazonaws.serverless.sample.springboot3.filter.CognitoIdentityFilter; + +import jakarta.servlet.Filter; + + +@SpringBootApplication +@Import({ PetsController.class }) +public class Application { + + // silence console logging + @Value("${logging.level.root:OFF}") + String message = ""; + + /* + * Create required HandlerMapping, to avoid several default HandlerMapping instances being created + */ + @Bean + public HandlerMapping handlerMapping() { + return new RequestMappingHandlerMapping(); + } + + /* + * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created + */ + @Bean + public HandlerAdapter handlerAdapter() { + return new RequestMappingHandlerAdapter(); + } + + @Bean("CognitoIdentityFilter") + public Filter cognitoFilter() { + return new CognitoIdentityFilter(); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} \ No newline at end of file diff --git a/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java new file mode 100644 index 000000000..769db35f3 --- /dev/null +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java @@ -0,0 +1,90 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.springboot3.controller; + + + +import com.amazonaws.serverless.sample.springboot3.model.Pet; +import com.amazonaws.serverless.sample.springboot3.model.PetData; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PathVariable; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import java.security.Principal; +import java.util.Optional; +import java.util.UUID; + + +@RestController +@EnableWebMvc +public class PetsController { + + @RequestMapping(path = "/pets", method = RequestMethod.POST) + public Pet createPet(@RequestBody Pet newPet) { + if (newPet.getName() == null || newPet.getBreed() == null) { + return null; + } + + Pet dbPet = newPet; + dbPet.setId(UUID.randomUUID().toString()); + return dbPet; + } + + @RequestMapping(path = "/pets", method = RequestMethod.GET) + public Pet[] listPets(@RequestParam("limit") Optional limit, Principal principal) { + int queryLimit = 10; + if (limit.isPresent()) { + queryLimit = limit.get(); + } + + Pet[] outputPets = new Pet[queryLimit]; + + for (int i = 0; i < queryLimit; i++) { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setName(PetData.getRandomName()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); + outputPets[i] = newPet; + } + + return outputPets; + } + + @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET) + public Pet listPets() { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); + newPet.setName(PetData.getRandomName()); + return newPet; + } + + @RequestMapping(path = "/foo/{gender}/bar/{age}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public String complexRequest(@RequestBody String body, + @PathVariable("gender") String gender, + @PathVariable("age") String age, + @RequestParam("name") String name + ) { + System.out.println("Body: " + body + " - " + gender + "/" + age + "/" + name); + return gender + "/" + age + "/" + name; + } + +} diff --git a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/filter/CognitoIdentityFilter.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java similarity index 87% rename from samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/filter/CognitoIdentityFilter.java rename to samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java index 5c3482e40..d6ccae765 100644 --- a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/filter/CognitoIdentityFilter.java +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java @@ -1,4 +1,4 @@ -package com.amazonaws.serverless.sample.springboot2.filter; +package com.amazonaws.serverless.sample.springboot3.filter; import com.amazonaws.serverless.proxy.RequestReader; @@ -7,12 +7,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import java.io.IOException; @@ -20,7 +20,7 @@ /** * Simple Filter implementation that looks for a Cognito identity id in the API Gateway request context * and stores the value in a request attribute. The filter is registered with aws-serverless-java-container - * in the onStartup method from the {@link com.amazonaws.serverless.sample.springboot2.StreamLambdaHandler} class. + * in the onStartup method from the {@link com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler} class. */ public class CognitoIdentityFilter implements Filter { public static final String COGNITO_IDENTITY_ATTRIBUTE = "com.amazonaws.serverless.cognitoId"; diff --git a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/model/Error.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java similarity index 93% rename from samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/model/Error.java rename to samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java index a2278ed02..320f21582 100644 --- a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/model/Error.java +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java @@ -10,7 +10,7 @@ * 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.sample.springboot2.model; +package com.amazonaws.serverless.sample.springboot3.model; public class Error { private String message; diff --git a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/model/Pet.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java similarity index 95% rename from samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/model/Pet.java rename to samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java index ee9a1e029..4f0c4ba8e 100644 --- a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/model/Pet.java +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java @@ -10,7 +10,7 @@ * 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.sample.springboot2.model; +package com.amazonaws.serverless.sample.springboot3.model; import java.util.Date; diff --git a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/model/PetData.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java similarity index 98% rename from samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/model/PetData.java rename to samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java index 34a324211..68ea3c18b 100644 --- a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/model/PetData.java +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java @@ -10,7 +10,7 @@ * 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.sample.springboot2.model; +package com.amazonaws.serverless.sample.springboot3.model; import java.util.ArrayList; diff --git a/samples/springboot3/alt-pet-store/src/main/resources/logback.xml b/samples/springboot3/alt-pet-store/src/main/resources/logback.xml new file mode 100644 index 000000000..81d891777 --- /dev/null +++ b/samples/springboot3/alt-pet-store/src/main/resources/logback.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/springboot3/alt-pet-store/template.yml b/samples/springboot3/alt-pet-store/template.yml new file mode 100644 index 000000000..c883f0850 --- /dev/null +++ b/samples/springboot3/alt-pet-store/template.yml @@ -0,0 +1,41 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Example Pet Store API written with spring-cloud-function web-proxy support + +Globals: + Api: + # API Gateway regional endpoints + EndpointConfiguration: REGIONAL + +Resources: + PetStoreFunction: + Type: AWS::Serverless::Function + Properties: +# AutoPublishAlias: bcn + FunctionName: pet-store-boot-3 + Handler: com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler::handleRequest + Runtime: java21 + SnapStart: + ApplyOn: PublishedVersions + CodeUri: . + MemorySize: 1024 + Policies: AWSLambdaBasicExecutionRole + Timeout: 30 + Environment: + Variables: + MAIN_CLASS: com.amazonaws.serverless.sample.springboot3.Application + Events: + HttpApiEvent: + Type: HttpApi + Properties: + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' + +Outputs: + SpringPetStoreApi: + Description: URL for application + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' + Export: + Name: SpringPetStoreApi + + diff --git a/samples/spark/pet-store/README.md b/samples/springboot3/graphql-pet-store/README.md similarity index 67% rename from samples/spark/pet-store/README.md rename to samples/springboot3/graphql-pet-store/README.md index 2bfec99de..e5bfad120 100644 --- a/samples/spark/pet-store/README.md +++ b/samples/springboot3/graphql-pet-store/README.md @@ -1,5 +1,6 @@ -# Serverless Spark example -A basic pet store written with the [Spark framework](http://sparkjava.com/). The `StreamLambdaHandler` object is the main entry point for Lambda. +# Serverless Spring Boot 3 with GraphQL example +A basic pet store written with the [Spring Boot 3 framework](https://projects.spring.io/spring-boot/). Unlike older examples, this example uses the [Spring for GraphQl](https://docs.spring.io/spring-graphql/reference/) library. + 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. @@ -22,15 +23,16 @@ To deploy the application in your AWS account, you can use the SAM CLI's guided $ sam deploy --guided ``` -Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` to make a call to the URL ``` ... --------------------------------------------------------------------------------------------------------- OutputKey-Description OutputValue --------------------------------------------------------------------------------------------------------- -PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/graphQl --------------------------------------------------------------------------------------------------------- -$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +$ curl -X POST https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/graphQl -d '{"query":"query petDetails {\n petById(id: \"pet-1\") {\n id\n name\n breed\n owner {\n id\n firstName\n lastName\n }\n }\n}","operationName":"petDetails"}' -H "Content-Type: application/json" + ``` \ No newline at end of file diff --git a/samples/springboot2/pet-store/pom.xml b/samples/springboot3/graphql-pet-store/pom.xml similarity index 88% rename from samples/springboot2/pet-store/pom.xml rename to samples/springboot3/graphql-pet-store/pom.xml index 8f7150db7..23ac0bae6 100644 --- a/samples/springboot2/pet-store/pom.xml +++ b/samples/springboot3/graphql-pet-store/pom.xml @@ -4,8 +4,8 @@ 4.0.0 com.amazonaws.serverless.sample - serverless-springboot2-example - 1.0-SNAPSHOT + serverless-springboot3-example + 2.0-SNAPSHOT Spring Boot example for the aws-serverless-java-container library Simple pet store written with the Spring framework and Spring Boot https://aws.amazon.com/lambda/ @@ -13,7 +13,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.5 + 3.5.0 @@ -25,11 +25,14 @@ - 1.8 - 1.8 + 17 + + org.springframework.boot + spring-boot-starter-graphql + org.springframework.boot spring-boot-starter-web @@ -40,18 +43,15 @@ - - com.amazonaws.serverless - aws-serverless-java-container-springboot2 - [1.6,) + org.springframework.graphql + spring-graphql-test + test - - - io.symphonia - lambda-logging - 1.0.3 + com.amazonaws.serverless + aws-serverless-java-container-springboot3 + [2.0.0-SNAPSHOT,),[2.0.0-M1,) @@ -63,7 +63,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 false @@ -97,7 +97,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.4.2 default-jar @@ -108,7 +108,7 @@ org.apache.maven.plugins maven-install-plugin - 3.0.0-M1 + 3.1.4 true @@ -117,7 +117,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.2.0 + 3.8.1 copy-dependencies @@ -135,7 +135,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.3.0 + 3.7.1 zip-assembly diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml b/samples/springboot3/graphql-pet-store/src/assembly/bin.xml similarity index 77% rename from aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml rename to samples/springboot3/graphql-pet-store/src/assembly/bin.xml index fcb935036..efc312c25 100644 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml +++ b/samples/springboot3/graphql-pet-store/src/assembly/bin.xml @@ -10,15 +10,10 @@ ${project.build.directory}${file.separator}lib + lib - websocket* - jetty-http* - jetty-client* - jetty-webapp* - jetty-xml* - jetty-io* + tomcat-embed* - lib @@ -29,4 +24,4 @@ ${file.separator} - \ No newline at end of file + diff --git a/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java new file mode 100644 index 000000000..9cf0ea610 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java @@ -0,0 +1,43 @@ +package com.amazonaws.serverless.sample.springboot3; + +import com.amazonaws.serverless.sample.springboot3.controller.PetsController; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + + +@SpringBootApplication +@Import({ PetsController.class }) +public class Application { + + // silence console logging + @Value("${logging.level.root:OFF}") + String message = ""; + + /* + * Create required HandlerMapping, to avoid several default HandlerMapping instances being created + */ + @Bean + public HandlerMapping handlerMapping() { + return new RequestMappingHandlerMapping(); + } + + /* + * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created + */ + @Bean + public HandlerAdapter handlerAdapter() { + return new RequestMappingHandlerAdapter(); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/StreamLambdaHandler.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java similarity index 76% rename from samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/StreamLambdaHandler.java rename to samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java index 01ea96630..a65c6f1ec 100644 --- a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/StreamLambdaHandler.java +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java @@ -1,4 +1,4 @@ -package com.amazonaws.serverless.sample.springboot2; +package com.amazonaws.serverless.sample.springboot3; import com.amazonaws.serverless.exceptions.ContainerInitializationException; @@ -6,12 +6,12 @@ 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.sample.springboot2.filter.CognitoIdentityFilter; +import com.amazonaws.serverless.sample.springboot3.filter.CognitoIdentityFilter; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import javax.servlet.DispatcherType; -import javax.servlet.FilterRegistration; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterRegistration; import java.io.IOException; import java.io.InputStream; @@ -25,13 +25,6 @@ public class StreamLambdaHandler implements RequestStreamHandler { try { handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class); - // For applications that take longer than 10 seconds to start, use the async builder: - // handler = new SpringBootProxyHandlerBuilder() - // .defaultProxy() - // .asyncInit() - // .springBootApplication(Application.class) - // .buildAndInitialize(); - // we use the onStartup method of the handler to register our custom filter handler.onStartup(servletContext -> { FilterRegistration.Dynamic registration = servletContext.addFilter("CognitoIdentityFilter", CognitoIdentityFilter.class); diff --git a/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java new file mode 100644 index 000000000..93eb999bd --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java @@ -0,0 +1,21 @@ +package com.amazonaws.serverless.sample.springboot3.controller; + +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.graphql.data.method.annotation.SchemaMapping; +import org.springframework.stereotype.Controller; +import com.amazonaws.serverless.sample.springboot3.model.Owner; +import com.amazonaws.serverless.sample.springboot3.model.Pet; + +@Controller +public class PetsController { + @QueryMapping + public Pet petById(@Argument String id) { + return Pet.getById(id); + } + + @SchemaMapping + public Owner owner(Pet pet) { + return Owner.getById(pet.ownerId()); + } +} diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/filter/CognitoIdentityFilter.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java similarity index 86% rename from samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/filter/CognitoIdentityFilter.java rename to samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java index 3b0f5dd1f..d6ccae765 100644 --- a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/filter/CognitoIdentityFilter.java +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java @@ -1,4 +1,4 @@ -package com.amazonaws.serverless.sample.spark.filter; +package com.amazonaws.serverless.sample.springboot3.filter; import com.amazonaws.serverless.proxy.RequestReader; @@ -7,12 +7,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import java.io.IOException; @@ -20,7 +20,7 @@ /** * Simple Filter implementation that looks for a Cognito identity id in the API Gateway request context * and stores the value in a request attribute. The filter is registered with aws-serverless-java-container - * in the onStartup method from the {@link com.amazonaws.serverless.sample.spring.StreamLambdaHandler} class. + * in the onStartup method from the {@link com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler} class. */ public class CognitoIdentityFilter implements Filter { public static final String COGNITO_IDENTITY_ATTRIBUTE = "com.amazonaws.serverless.cognitoId"; @@ -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/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Owner.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Owner.java new file mode 100644 index 000000000..f048e6906 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Owner.java @@ -0,0 +1,20 @@ +package com.amazonaws.serverless.sample.springboot3.model; + +import java.util.Arrays; +import java.util.List; + +public record Owner (String id, String firstName, String lastName) { + + private static List owners = Arrays.asList( + new Owner("owner-1", "Joshua", "Bloch"), + new Owner("owner-2", "Douglas", "Adams"), + new Owner("owner-3", "Bill", "Bryson") + ); + + public static Owner getById(String id) { + return owners.stream() + .filter(owner -> owner.id().equals(id)) + .findFirst() + .orElse(null); + } +} diff --git a/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java new file mode 100644 index 000000000..f97b973d0 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java @@ -0,0 +1,20 @@ +package com.amazonaws.serverless.sample.springboot3.model; + +import java.util.Arrays; +import java.util.List; + +public record Pet (String id, String name, String breed, String ownerId) { + + private static List pets = Arrays.asList( + new Pet("pet-1", "Alpha", "Bulldog", "owner-1"), + new Pet("pet-2", "Max", "German Shepherd", "owner-2"), + new Pet("pet-3", "Rockie", "Golden Retriever", "owner-3") + ); + + public static Pet getById(String id) { + return pets.stream() + .filter(pet -> pet.id().equals(id)) + .findFirst() + .orElse(null); + } +} diff --git a/samples/springboot3/graphql-pet-store/src/main/resources/graphql/schema.graphqls b/samples/springboot3/graphql-pet-store/src/main/resources/graphql/schema.graphqls new file mode 100644 index 000000000..293cdcc40 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/resources/graphql/schema.graphqls @@ -0,0 +1,16 @@ +type Query { + petById(id: ID): Pet +} + +type Pet { + id: ID + name: String + breed: String + owner: Owner +} + +type Owner { + id: ID + firstName: String + lastName: String +} diff --git a/samples/springboot3/graphql-pet-store/src/main/resources/logback.xml b/samples/springboot3/graphql-pet-store/src/main/resources/logback.xml new file mode 100644 index 000000000..8ff988992 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/resources/logback.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/samples/struts/pet-store/template.yml b/samples/springboot3/graphql-pet-store/template.yml similarity index 61% rename from samples/struts/pet-store/template.yml rename to samples/springboot3/graphql-pet-store/template.yml index 85870c6bd..ce5dcc6b1 100644 --- a/samples/struts/pet-store/template.yml +++ b/samples/springboot3/graphql-pet-store/template.yml @@ -1,6 +1,6 @@ AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 -Description: Example Pet Store API written with Apache Struts based on the aws-serverless-java-container library +Description: Example Pet Store API written with SpringBoot3, Spring for GraphQl and the aws-serverless-java-container library Globals: Api: @@ -11,12 +11,12 @@ Resources: PetStoreFunction: Type: AWS::Serverless::Function Properties: - Handler: com.amazonaws.serverless.proxy.struts.StrutsLambdaHandler::handleRequest - Runtime: java11 + Handler: com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler::handleRequest + Runtime: java21 CodeUri: . - MemorySize: 512 + MemorySize: 1024 Policies: AWSLambdaBasicExecutionRole - Timeout: 30 + Timeout: 60 Events: HttpApiEvent: Type: HttpApi @@ -25,8 +25,8 @@ Resources: PayloadFormatVersion: '1.0' Outputs: - StrutsPetStoreApi: + SpringBootPetStoreApi: Description: URL for application - Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/graphql' Export: - Name: StrutsPetStoreApi + Name: SpringBootPetStoreApi diff --git a/samples/springboot3/pet-store-native/.gitignore b/samples/springboot3/pet-store-native/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/samples/springboot3/pet-store-native/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/samples/springboot3/pet-store-native/.mvn/wrapper/maven-wrapper.jar b/samples/springboot3/pet-store-native/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..cb28b0e37 Binary files /dev/null and b/samples/springboot3/pet-store-native/.mvn/wrapper/maven-wrapper.jar differ diff --git a/samples/springboot3/pet-store-native/.mvn/wrapper/maven-wrapper.properties b/samples/springboot3/pet-store-native/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..7d02699af --- /dev/null +++ b/samples/springboot3/pet-store-native/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/samples/springboot3/pet-store-native/Dockerfile b/samples/springboot3/pet-store-native/Dockerfile new file mode 100644 index 000000000..7d5833bfe --- /dev/null +++ b/samples/springboot3/pet-store-native/Dockerfile @@ -0,0 +1,37 @@ +FROM public.ecr.aws/amazonlinux/amazonlinux:2023 + +RUN yum -y update \ + && yum install -y unzip tar gzip bzip2-devel ed gcc gcc-c++ gcc-gfortran \ + less libcurl-devel openssl openssl-devel readline-devel xz-devel \ + zlib-devel glibc-static zlib-static \ + && rm -rf /var/cache/yum + +# Graal VM +ENV GRAAL_VERSION 21.0.2 +ENV ARCHITECTURE aarch64 +ENV GRAAL_FILENAME graalvm-community-jdk-${GRAAL_VERSION}_linux-${ARCHITECTURE}_bin.tar.gz +RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${GRAAL_VERSION}/${GRAAL_FILENAME} | tar -xvz +RUN mv graalvm-community-openjdk-${GRAAL_VERSION}* /usr/lib/graalvm +ENV JAVA_HOME /usr/lib/graalvm + +# Maven +ENV MVN_VERSION 3.9.9 +ENV MVN_FOLDERNAME apache-maven-${MVN_VERSION} +ENV MVN_FILENAME apache-maven-${MVN_VERSION}-bin.tar.gz +RUN curl -4 -L https://archive.apache.org/dist/maven/maven-3/${MVN_VERSION}/binaries/${MVN_FILENAME} | tar -xvz +RUN mv $MVN_FOLDERNAME /usr/lib/maven +RUN ln -s /usr/lib/maven/bin/mvn /usr/bin/mvn + +# Gradle +ENV GRADLE_VERSION 7.4.1 +ENV GRADLE_FOLDERNAME gradle-${GRADLE_VERSION} +ENV GRADLE_FILENAME gradle-${GRADLE_VERSION}-bin.zip +RUN curl -LO https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip +RUN unzip gradle-${GRADLE_VERSION}-bin.zip +RUN mv $GRADLE_FOLDERNAME /usr/lib/gradle +RUN ln -s /usr/lib/gradle/bin/gradle /usr/bin/gradle + +VOLUME /project +WORKDIR /project + +WORKDIR /pet-store-native diff --git a/samples/springboot3/pet-store-native/README.md b/samples/springboot3/pet-store-native/README.md new file mode 100644 index 000000000..571d56198 --- /dev/null +++ b/samples/springboot3/pet-store-native/README.md @@ -0,0 +1,38 @@ +In this sample, you'll build a native GraalVM image for running web workloads in AWS Lambda. + + +## To build the sample + +You first need to build the function, then you will deploy it to AWS Lambda. + +Please note that the sample is for `x86` architectures. In case you want to build and run it on ARM, e.g. Apple Mac M1, M2, ... +you must change the according line in the `Dockerfile` to `ENV ARCHITECTURE aarch64`. +In addition, uncomment the `arm64` Architectures section in `template.yml`. + +### Step 1 - Build the native image + +Before starting the build, you must clone or download the code in **pet-store-native**. + +1. Change into the project directory: `samples/springboot3/pet-store-native` +2. Run the following to build a Docker container image which will include all the necessary dependencies to build the application + ``` + docker build -t al2023-graalvm21:native-web . + ``` +3. Build the application within the previously created build image + ``` + docker run -it -v `pwd`:`pwd` -w `pwd` -v ~/.m2:/root/.m2 al2023-graalvm21:native-web ./mvnw clean -Pnative package -DskipTests + ``` +4. After the build finishes, you need to deploy the function: + ``` + sam deploy --guided + ``` + +This will deploy your application and will attach an AWS API Gateway +Once the deployment is finished you should see the following: +``` +Key ServerlessWebNativeApi +Description URL for application +Value https://xxxxxxxx.execute-api.us-east-2.amazonaws.com/pets +``` + +You can now simply execute GET on this URL and see the listing fo all pets. diff --git a/samples/springboot3/pet-store-native/mvnw b/samples/springboot3/pet-store-native/mvnw new file mode 100755 index 000000000..8d937f4c1 --- /dev/null +++ b/samples/springboot3/pet-store-native/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/samples/springboot3/pet-store-native/mvnw.cmd b/samples/springboot3/pet-store-native/mvnw.cmd new file mode 100644 index 000000000..f80fbad3e --- /dev/null +++ b/samples/springboot3/pet-store-native/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/samples/springboot3/pet-store-native/pom.xml b/samples/springboot3/pet-store-native/pom.xml new file mode 100644 index 000000000..960442014 --- /dev/null +++ b/samples/springboot3/pet-store-native/pom.xml @@ -0,0 +1,134 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.5 + + + com.amazonaws.serverless.sample + pet-store-native + 0.0.1-SNAPSHOT + pet-store-native + Sample of AWS with Spring Native + + 17 + + + + org.springframework.boot + spring-boot-starter + + + com.amazonaws.serverless + aws-serverless-java-container-springboot3 + [2.0.0-SNAPSHOT,),[2.0.0-M1,) + + + + org.crac + crac + runtime + + + com.amazonaws + aws-lambda-java-events + 3.15.0 + + + com.amazonaws + aws-lambda-java-core + 1.2.3 + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + native + + + + org.springframework.boot + spring-boot-maven-plugin + + -agentlib:native-image-agent=config-merge-dir=src/main/resources/META-INF/native-image/ --enable-preview + + + + + org.graalvm.buildtools + native-maven-plugin + + + --enable-url-protocols=http + -march=compatibility + + + + + + build + + package + + + test + + test + + test + + + + + maven-assembly-plugin + + + native-zip + package + + single + + false + + + + + src/assembly/native.xml + + + + + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + \ No newline at end of file diff --git a/samples/springboot3/pet-store-native/src/assembly/java.xml b/samples/springboot3/pet-store-native/src/assembly/java.xml new file mode 100644 index 000000000..bd4961b58 --- /dev/null +++ b/samples/springboot3/pet-store-native/src/assembly/java.xml @@ -0,0 +1,31 @@ + + java-zip + + zip + + + + + target/classes + / + + + src/shell/java + / + true + 0775 + + bootstrap + + + + + + /lib + false + runtime + + + \ No newline at end of file diff --git a/samples/springboot3/pet-store-native/src/assembly/native.xml b/samples/springboot3/pet-store-native/src/assembly/native.xml new file mode 100644 index 000000000..9bd97a5b7 --- /dev/null +++ b/samples/springboot3/pet-store-native/src/assembly/native.xml @@ -0,0 +1,29 @@ + + native-zip + + zip + + + + + src/shell/native + / + true + 0775 + + bootstrap + + + + target + / + true + 0775 + + pet-store-native + + + + \ No newline at end of file diff --git a/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/DemoApplication.java b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/DemoApplication.java new file mode 100644 index 000000000..bf53a01f8 --- /dev/null +++ b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/DemoApplication.java @@ -0,0 +1,12 @@ +package com.amazonaws.serverless.sample.springboot3; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) throws Exception { + SpringApplication.run(DemoApplication.class, args); + } +} diff --git a/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/HelloController.java b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/HelloController.java new file mode 100644 index 000000000..2c3fcfb01 --- /dev/null +++ b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/HelloController.java @@ -0,0 +1,17 @@ +package com.amazonaws.serverless.sample.springboot3; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + public HelloController() { + System.out.println("Creating controller"); + } + + @GetMapping("/hello") + public String something(){ + return "Hello World"; + } +} diff --git a/samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/PetsController.java b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java similarity index 56% rename from samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/PetsController.java rename to samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java index a0d43e617..849286fec 100644 --- a/samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/PetsController.java +++ b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java @@ -10,32 +10,33 @@ * 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.micronaut.demo; +package com.amazonaws.serverless.sample.springboot3.controller; -import com.amazonaws.micronaut.demo.model.Pet; -import com.amazonaws.micronaut.demo.model.PetData; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; + + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import javax.annotation.Nullable; +import com.amazonaws.serverless.sample.springboot3.model.Pet; +import com.amazonaws.serverless.sample.springboot3.model.PetData; + import java.security.Principal; import java.util.Optional; import java.util.UUID; + @RestController @EnableWebMvc public class PetsController { - - private PetData petData; - - @Autowired - public PetsController(PetData data) { - petData = data; - } - - @RequestMapping(path = "/pets", method = RequestMethod.POST) + @PostMapping(path = "/pets") public Pet createPet(@RequestBody Pet newPet) { + System.out.println("==> Creating Pet: " + newPet); if (newPet.getName() == null || newPet.getBreed() == null) { return null; } @@ -45,8 +46,9 @@ public Pet createPet(@RequestBody Pet newPet) { return dbPet; } - @RequestMapping(path = "/pets", method = RequestMethod.GET) - public Pet[] listPets(@RequestParam("limit") Optional limit, @Nullable Principal principal) { + @GetMapping(path = "/pets") + public Pet[] listPets(@RequestParam("limit") Optional limit, Principal principal) { + System.out.println("==> Listing Pets"); int queryLimit = 10; if (limit.isPresent()) { queryLimit = limit.get(); @@ -57,22 +59,23 @@ public Pet[] listPets(@RequestParam("limit") Optional limit, @Nullable for (int i = 0; i < queryLimit; i++) { Pet newPet = new Pet(); newPet.setId(UUID.randomUUID().toString()); - newPet.setName(petData.getRandomName()); - newPet.setBreed(petData.getRandomBreed()); - newPet.setDateOfBirth(petData.getRandomDoB()); + newPet.setName(PetData.getRandomName()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); outputPets[i] = newPet; } return outputPets; } - @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET) - public Pet getPet(@RequestParam("petId") String petId) { + @GetMapping(path = "/pets/{petId}") + public Pet listPets() { + System.out.println("==> Listing Pets"); Pet newPet = new Pet(); newPet.setId(UUID.randomUUID().toString()); - newPet.setBreed(petData.getRandomBreed()); - newPet.setDateOfBirth(petData.getRandomDoB()); - newPet.setName(petData.getRandomName()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); + newPet.setName(PetData.getRandomName()); return newPet; } diff --git a/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java new file mode 100644 index 000000000..d6ccae765 --- /dev/null +++ b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java @@ -0,0 +1,69 @@ +package com.amazonaws.serverless.sample.springboot3.filter; + + +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +import java.io.IOException; + + +/** + * Simple Filter implementation that looks for a Cognito identity id in the API Gateway request context + * and stores the value in a request attribute. The filter is registered with aws-serverless-java-container + * in the onStartup method from the {@link com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler} class. + */ +public class CognitoIdentityFilter implements Filter { + public static final String COGNITO_IDENTITY_ATTRIBUTE = "com.amazonaws.serverless.cognitoId"; + + private static Logger log = LoggerFactory.getLogger(CognitoIdentityFilter.class); + + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + // nothing to do in init + } + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + Object apiGwContext = servletRequest.getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY); + 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"); + filterChain.doFilter(servletRequest, servletResponse); + } + + AwsProxyRequestContext ctx = (AwsProxyRequestContext)apiGwContext; + if (ctx.getIdentity() == null) { + log.warn("Identity context is null"); + filterChain.doFilter(servletRequest, servletResponse); + } + String cognitoIdentityId = ctx.getIdentity().getCognitoIdentityId(); + if (cognitoIdentityId == null || "".equals(cognitoIdentityId.trim())) { + log.warn("Cognito identity id in request is null"); + } + servletRequest.setAttribute(COGNITO_IDENTITY_ATTRIBUTE, cognitoIdentityId); + filterChain.doFilter(servletRequest, servletResponse); + } + + + @Override + public void destroy() { + // nothing to do in destroy + } +} diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Error.java b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java similarity index 93% rename from samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Error.java rename to samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java index 3577e6a3a..320f21582 100644 --- a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Error.java +++ b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java @@ -10,7 +10,7 @@ * 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.sample.spark.model; +package com.amazonaws.serverless.sample.springboot3.model; public class Error { private String message; diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Pet.java b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java similarity index 95% rename from samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Pet.java rename to samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java index a6f569597..4f0c4ba8e 100644 --- a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Pet.java +++ b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java @@ -10,10 +10,11 @@ * 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.sample.spark.model; +package com.amazonaws.serverless.sample.springboot3.model; import java.util.Date; + public class Pet { private String id; private String breed; diff --git a/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/model/PetData.java b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java similarity index 94% rename from samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/model/PetData.java rename to samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java index 84be64eab..68ea3c18b 100644 --- a/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/model/PetData.java +++ b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java @@ -10,11 +10,17 @@ * 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.sample.struts.model; +package com.amazonaws.serverless.sample.springboot3.model; -import java.util.*; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; import java.util.concurrent.ThreadLocalRandom; + public class PetData { private static List breeds = new ArrayList<>(); static { diff --git a/samples/springboot3/pet-store-native/src/main/resources/META-INF/.gitignore b/samples/springboot3/pet-store-native/src/main/resources/META-INF/.gitignore new file mode 100644 index 000000000..0726bbaa2 --- /dev/null +++ b/samples/springboot3/pet-store-native/src/main/resources/META-INF/.gitignore @@ -0,0 +1 @@ +/native-image/ diff --git a/samples/springboot3/pet-store-native/src/main/resources/application.properties b/samples/springboot3/pet-store-native/src/main/resources/application.properties new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/samples/springboot3/pet-store-native/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/samples/springboot3/pet-store-native/src/shell/java/bootstrap b/samples/springboot3/pet-store-native/src/shell/java/bootstrap new file mode 100644 index 000000000..4586728a7 --- /dev/null +++ b/samples/springboot3/pet-store-native/src/shell/java/bootstrap @@ -0,0 +1,7 @@ +#!/bin/sh + +cd ${LAMBDA_TASK_ROOT:-.} + +java -Dspring.main.web-application-type=none -Dlogging.level.org.springframework=DEBUG \ + -noverify -XX:TieredStopAtLevel=1 -Xss256K -XX:MaxMetaspaceSize=128M \ + -cp .:`echo lib/*.jar | tr ' ' :` com.amazonaws.serverless.sample.springboot3.DemoApplication \ No newline at end of file diff --git a/samples/springboot3/pet-store-native/src/shell/native/bootstrap b/samples/springboot3/pet-store-native/src/shell/native/bootstrap new file mode 100644 index 000000000..0156b090b --- /dev/null +++ b/samples/springboot3/pet-store-native/src/shell/native/bootstrap @@ -0,0 +1,5 @@ +#!/bin/sh + +cd ${LAMBDA_TASK_ROOT:-.} + +./pet-store-native -Dlogging.level.org.springframework=DEBUG -Dlogging.level.com.amazonaws.serverless.proxy.spring=DEBUG diff --git a/samples/spark/pet-store/template.yml b/samples/springboot3/pet-store-native/template.yaml similarity index 56% rename from samples/spark/pet-store/template.yml rename to samples/springboot3/pet-store-native/template.yaml index 535e684f9..dc05e1be7 100644 --- a/samples/spark/pet-store/template.yml +++ b/samples/springboot3/pet-store-native/template.yaml @@ -1,32 +1,34 @@ AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 -Description: Example Pet Store API written with spark with the aws-serverless-java-container library - -Globals: - Api: - # API Gateway regional endpoints - EndpointConfiguration: REGIONAL - +Description: Serverless Java Container GraalVM Resources: - PetStoreFunction: + ServerlessWebNativeFunction: Type: AWS::Serverless::Function Properties: - Handler: com.amazonaws.serverless.sample.spark.StreamLambdaHandler::handleRequest - Runtime: java11 - CodeUri: . MemorySize: 512 - Policies: AWSLambdaBasicExecutionRole - Timeout: 20 + FunctionName: pet-store-native + Timeout: 15 + CodeUri: ./target/pet-store-native-0.0.1-SNAPSHOT-native-zip.zip + Handler: NOP + Runtime: provided.al2023 +# If you want to build for ARM64 uncomment the following lines +# Architectures: +# - arm64 Events: HttpApiEvent: Type: HttpApi Properties: TimeoutInMillis: 20000 PayloadFormatVersion: '1.0' - + +Globals: + Api: + # API Gateway regional endpoints + EndpointConfiguration: REGIONAL Outputs: - SparkPetStoreApi: + ServerlessWebNativeApi: Description: URL for application Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' Export: - Name: SparkPetStoreApi + Name: ServerlessWebNativeApi + \ No newline at end of file diff --git a/samples/springboot2/pet-store/README.md b/samples/springboot3/pet-store/README.md similarity index 94% rename from samples/springboot2/pet-store/README.md rename to samples/springboot3/pet-store/README.md index 8e15b3773..fb3fab3b0 100644 --- a/samples/springboot2/pet-store/README.md +++ b/samples/springboot3/pet-store/README.md @@ -1,5 +1,5 @@ -# Serverless Spring Boot 2 example -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. +# Serverless Spring Boot 3 example +A basic pet store written with the [Spring Boot 3 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 `template.yml` file in the root folder contains the application definition. diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle b/samples/springboot3/pet-store/build.gradle similarity index 54% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle rename to samples/springboot3/pet-store/build.gradle index 73c0bdb60..653135db0 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle +++ b/samples/springboot3/pet-store/build.gradle @@ -1,19 +1,19 @@ apply plugin: 'java' repositories { - jcenter() mavenLocal() mavenCentral() + maven {url "https://repo.spring.io/milestone"} + maven {url "https://repo.spring.io/snapshot"} } dependencies { implementation ( - 'org.springframework.boot:spring-boot-starter-web:2.7.5', - 'com.amazonaws.serverless:aws-serverless-java-container-springboot2:[1.0,)', - 'io.symphonia:lambda-logging:1.0.3' + implementation('org.springframework.boot:spring-boot-starter-web:3.4.5') { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' + }, + 'com.amazonaws.serverless:aws-serverless-java-container-springboot3:[2.0-SNAPSHOT,)', ) - - testImplementation("junit:junit:4.13.2") } task buildZip(type: Zip) { diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml b/samples/springboot3/pet-store/pom.xml similarity index 62% rename from aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml rename to samples/springboot3/pet-store/pom.xml index 0381379f0..4126ead75 100644 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml +++ b/samples/springboot3/pet-store/pom.xml @@ -1,49 +1,49 @@ -#set($dollar = '$') - 4.0.0 - \${groupId} - \${artifactId} - \${version} - jar + com.amazonaws.serverless.sample + serverless-springboot3-example + 2.0-SNAPSHOT + Spring Boot example for the aws-serverless-java-container library + Simple pet store written with the Spring framework and Spring Boot + https://aws.amazon.com/lambda/ - Serverless Spark API - https://github.com/awslabs/aws-serverless-java-container + + org.springframework.boot + spring-boot-starter-parent + 3.5.0 + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + - 1.8 - 1.8 - 2.14.0 - 2.9.1 + 17 - com.amazonaws.serverless - aws-serverless-java-container-spark - ${project.version} - - - - com.sparkjava - spark-core - \${spark.version} + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + - com.fasterxml.jackson.core - jackson-databind - \${jackson.version} - - - - junit - junit - 4.12 - test + com.amazonaws.serverless + aws-serverless-java-container-springboot3 + [2.0.0-SNAPSHOT,),[2.0.0-M1,) @@ -55,7 +55,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 false @@ -68,15 +68,7 @@ - - org.eclipse.jetty.websocket:* - org.eclipse.jetty:jetty-http - org.eclipse.jetty:jetty-client - org.eclipse.jetty:jetty-webapp - org.eclipse.jetty:jetty-xml - org.eclipse.jetty:jetty-io + org.apache.tomcat.embed:* @@ -97,7 +89,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.4.2 default-jar @@ -108,7 +100,7 @@ org.apache.maven.plugins maven-install-plugin - 3.0.0-M1 + 3.1.4 true @@ -117,7 +109,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.2.0 + 3.8.1 copy-dependencies @@ -126,7 +118,7 @@ copy-dependencies - ${dollar}{project.build.directory}${dollar}{file.separator}lib + ${project.build.directory}/lib runtime @@ -135,7 +127,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.3.0 + 3.7.1 zip-assembly @@ -144,9 +136,9 @@ single - ${dollar}{project.artifactId}-${dollar}{project.version} + ${project.artifactId}-${project.version} - src${dollar}{file.separator}assembly${dollar}{file.separator}bin.xml + src${file.separator}assembly${file.separator}bin.xml false @@ -157,4 +149,6 @@ + + diff --git a/samples/spark/pet-store/src/assembly/bin.xml b/samples/springboot3/pet-store/src/assembly/bin.xml similarity index 78% rename from samples/spark/pet-store/src/assembly/bin.xml rename to samples/springboot3/pet-store/src/assembly/bin.xml index fcb935036..1e085057d 100644 --- a/samples/spark/pet-store/src/assembly/bin.xml +++ b/samples/springboot3/pet-store/src/assembly/bin.xml @@ -10,15 +10,10 @@ ${project.build.directory}${file.separator}lib + lib - websocket* - jetty-http* - jetty-client* - jetty-webapp* - jetty-xml* - jetty-io* + tomcat-embed* - lib diff --git a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/Application.java b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java similarity index 87% rename from samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/Application.java rename to samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java index d613fc073..c576521f1 100644 --- a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/Application.java +++ b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java @@ -1,6 +1,6 @@ -package com.amazonaws.serverless.sample.springboot2; +package com.amazonaws.serverless.sample.springboot3; -import com.amazonaws.serverless.sample.springboot2.controller.PetsController; +import com.amazonaws.serverless.sample.springboot3.controller.PetsController; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; @@ -15,8 +15,8 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; @SpringBootApplication diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/StreamLambdaHandler.java b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java similarity index 69% rename from samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/StreamLambdaHandler.java rename to samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java index 40ace9d33..a65c6f1ec 100644 --- a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/StreamLambdaHandler.java +++ b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java @@ -1,19 +1,17 @@ -package com.amazonaws.serverless.sample.spark; +package com.amazonaws.serverless.sample.springboot3; import com.amazonaws.serverless.exceptions.ContainerInitializationException; 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.spark.SparkLambdaContainerHandler; -import com.amazonaws.serverless.sample.spark.filter.CognitoIdentityFilter; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.serverless.sample.springboot3.filter.CognitoIdentityFilter; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import spark.Spark; - -import javax.servlet.DispatcherType; -import javax.servlet.FilterRegistration; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterRegistration; import java.io.IOException; import java.io.InputStream; @@ -22,22 +20,20 @@ public class StreamLambdaHandler implements RequestStreamHandler { - private static SparkLambdaContainerHandler handler; + private static SpringBootLambdaContainerHandler handler; static { try { - handler = SparkLambdaContainerHandler.getAwsProxyHandler(); - SparkResources.defineResources(); - Spark.awaitInitialization(); + handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class); // we use the onStartup method of the handler to register our custom filter handler.onStartup(servletContext -> { FilterRegistration.Dynamic registration = servletContext.addFilter("CognitoIdentityFilter", CognitoIdentityFilter.class); - registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*"); + registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); }); } catch (ContainerInitializationException e) { // if we fail here. We re-throw the exception to force another cold start e.printStackTrace(); - throw new RuntimeException("Could not initialize Spark container", e); + throw new RuntimeException("Could not initialize Spring Boot application", e); } } diff --git a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/controller/PetsController.java b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java similarity index 92% rename from samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/controller/PetsController.java rename to samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java index bbf69b084..680e629d3 100644 --- a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/controller/PetsController.java +++ b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java @@ -10,12 +10,12 @@ * 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.sample.springboot2.controller; +package com.amazonaws.serverless.sample.springboot3.controller; -import com.amazonaws.serverless.sample.springboot2.model.Pet; -import com.amazonaws.serverless.sample.springboot2.model.PetData; +import com.amazonaws.serverless.sample.springboot3.model.Pet; +import com.amazonaws.serverless.sample.springboot3.model.PetData; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java new file mode 100644 index 000000000..d6ccae765 --- /dev/null +++ b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java @@ -0,0 +1,69 @@ +package com.amazonaws.serverless.sample.springboot3.filter; + + +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +import java.io.IOException; + + +/** + * Simple Filter implementation that looks for a Cognito identity id in the API Gateway request context + * and stores the value in a request attribute. The filter is registered with aws-serverless-java-container + * in the onStartup method from the {@link com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler} class. + */ +public class CognitoIdentityFilter implements Filter { + public static final String COGNITO_IDENTITY_ATTRIBUTE = "com.amazonaws.serverless.cognitoId"; + + private static Logger log = LoggerFactory.getLogger(CognitoIdentityFilter.class); + + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + // nothing to do in init + } + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + Object apiGwContext = servletRequest.getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY); + 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"); + filterChain.doFilter(servletRequest, servletResponse); + } + + AwsProxyRequestContext ctx = (AwsProxyRequestContext)apiGwContext; + if (ctx.getIdentity() == null) { + log.warn("Identity context is null"); + filterChain.doFilter(servletRequest, servletResponse); + } + String cognitoIdentityId = ctx.getIdentity().getCognitoIdentityId(); + if (cognitoIdentityId == null || "".equals(cognitoIdentityId.trim())) { + log.warn("Cognito identity id in request is null"); + } + servletRequest.setAttribute(COGNITO_IDENTITY_ATTRIBUTE, cognitoIdentityId); + filterChain.doFilter(servletRequest, servletResponse); + } + + + @Override + public void destroy() { + // nothing to do in destroy + } +} diff --git a/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java new file mode 100644 index 000000000..320f21582 --- /dev/null +++ b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.springboot3.model; + +public class Error { + private String message; + + public Error(String errorMessage) { + message = errorMessage; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/model/Pet.java b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java similarity index 81% rename from samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/model/Pet.java rename to samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java index 3f0328ee5..4f0c4ba8e 100644 --- a/samples/micronaut/pet-store/src/main/java/com/amazonaws/micronaut/demo/model/Pet.java +++ b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java @@ -10,22 +10,17 @@ * 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.micronaut.demo.model; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.micronaut.core.annotation.Introspected; +package com.amazonaws.serverless.sample.springboot3.model; import java.util.Date; -@Introspected + public class Pet { private String id; private String breed; private String name; private Date dateOfBirth; - @JsonProperty("petId") public String getId() { return id; } @@ -50,7 +45,6 @@ public void setName(String name) { this.name = name; } - @JsonFormat(pattern = "YYYY-mm-dd") public Date getDateOfBirth() { return dateOfBirth; } diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/PetData.java b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java similarity index 94% rename from samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/PetData.java rename to samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java index ba9c47db6..68ea3c18b 100644 --- a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/PetData.java +++ b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java @@ -10,11 +10,17 @@ * 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.sample.spark.model; +package com.amazonaws.serverless.sample.springboot3.model; -import java.util.*; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; import java.util.concurrent.ThreadLocalRandom; + public class PetData { private static List breeds = new ArrayList<>(); static { diff --git a/samples/springboot2/pet-store/src/main/resources/logback.xml b/samples/springboot3/pet-store/src/main/resources/logback.xml similarity index 100% rename from samples/springboot2/pet-store/src/main/resources/logback.xml rename to samples/springboot3/pet-store/src/main/resources/logback.xml diff --git a/samples/springboot2/pet-store/template.yml b/samples/springboot3/pet-store/template.yml similarity index 90% rename from samples/springboot2/pet-store/template.yml rename to samples/springboot3/pet-store/template.yml index c7a17276f..a8474349b 100644 --- a/samples/springboot2/pet-store/template.yml +++ b/samples/springboot3/pet-store/template.yml @@ -11,8 +11,8 @@ Resources: PetStoreFunction: Type: AWS::Serverless::Function Properties: - Handler: com.amazonaws.serverless.sample.springboot2.StreamLambdaHandler::handleRequest - Runtime: java11 + Handler: com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler::handleRequest + Runtime: java21 CodeUri: . MemorySize: 1512 Policies: AWSLambdaBasicExecutionRole diff --git a/samples/struts/pet-store/README.md b/samples/struts/pet-store/README.md deleted file mode 100644 index bc5c047ae..000000000 --- a/samples/struts/pet-store/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Serverless Struts example -A basic pet store written with the [Apache Struts framework](https://struts.apache.org). The `StrutsLambdaHandler` object provided by the `aws-serverless-java-container-struts` is the main entry point for Lambda. - -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition - -## Pre-requisites -* [AWS CLI](https://aws.amazon.com/cli/) -* [SAM CLI](https://github.com/awslabs/aws-sam-cli) -* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) - - -## Deployment -In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package -``` -$ mvn package && sam build -``` - -### Test Local - -``` -$ sam local invoke -e test-event.json -``` - -### Deploy Sample Application - -This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. - -To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen - -``` -$ sam deploy --guided -``` - -Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL - -``` -... ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Outputs ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Key StrutsPetStoreApi -Description URL for application -Value https://xxxxxxxxxx.execute-api..amazonaws.com/pets ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -``` -## Test - -### JSON Request: -``` -$ curl https://xxxxxxxxxx.execute-api..amazonaws.com/pets.json -``` - -### XML Request -``` -$ curl https://xxxxxxxxxx.execute-api..amazonaws.com/pets.xml -``` \ No newline at end of file diff --git a/samples/struts/pet-store/build.gradle b/samples/struts/pet-store/build.gradle deleted file mode 100644 index f7d6af5e8..000000000 --- a/samples/struts/pet-store/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -apply plugin: 'java' - -repositories { - mavenLocal() - mavenCentral() -} - -configurations { - implementation { - exclude group: 'org.apache.logging.log4j', module: 'log4j-to-slf4j' - } -} - -dependencies { - implementation ( - 'com.amazonaws.serverless:aws-serverless-java-container-struts:[1.9,)', - 'org.apache.struts:struts2-convention-plugin:6.0.3', - 'org.apache.struts:struts2-rest-plugin:6.0.3', - 'org.apache.struts:struts2-bean-validation-plugin:6.0.3', - 'org.apache.struts:struts2-junit-plugin:6.0.3', - 'com.jgeppert.struts2:struts2-aws-lambda-support-plugin:1.4.2', - 'org.hibernate.validator:hibernate-validator:6.1.7.Final', - 'org.glassfish:javax.el:3.0.0', - 'javax.el:javax.el-api:3.0.0', - 'com.fasterxml.jackson.core:jackson-databind:2.14.0', - 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0', - 'org.apache.logging.log4j:log4j-core:2.19.0', - 'org.apache.logging.log4j:log4j-api:2.19.0', - 'com.amazonaws:aws-lambda-java-log4j2:1.5.1', - ) -} - -task buildZip(type: Zip) { - from compileJava - from processResources - into('lib') { - from(configurations.compileClasspath) - } -} - -build.dependsOn buildZip diff --git a/samples/struts/pet-store/pom.xml b/samples/struts/pet-store/pom.xml deleted file mode 100644 index 3f6b1d7b8..000000000 --- a/samples/struts/pet-store/pom.xml +++ /dev/null @@ -1,176 +0,0 @@ - - - 4.0.0 - - com.amazonaws.serverless.sample - serverless-struts-example - 1.0-SNAPSHOT - Struts example for the aws-serverless-java-container library - Simple pet store written with the Apache Struts framework - https://aws.amazon.com/lambda/ - - - https://github.com/awslabs/aws-serverless-java-container.git - - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - 1.8 - 1.8 - 6.0.3 - 2.14.0 - 4.13.2 - 2.19.0 - - - - - com.amazonaws.serverless - aws-serverless-java-container-struts - [1.9,) - - - - com.amazonaws - aws-lambda-java-core - 1.2.1 - - - - org.apache.struts - struts2-convention-plugin - ${struts.version} - - - - org.apache.struts - struts2-rest-plugin - ${struts.version} - - - - org.apache.struts - struts2-bean-validation-plugin - ${struts.version} - - - - org.apache.struts - struts2-junit-plugin - ${struts.version} - test - - - - - com.jgeppert.struts2 - struts2-aws-lambda-support-plugin - 1.4.2 - - - - - org.hibernate.validator - hibernate-validator - 6.1.7.Final - - - org.glassfish - javax.el - 3.0.0 - - - javax.el - javax.el-api - 3.0.0 - - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - ${jackson.version} - - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - - - - org.apache.logging.log4j - log4j-api - ${log4j.version} - - - - com.amazonaws - aws-lambda-java-log4j2 - 1.5.1 - - - - junit - junit - ${junit.version} - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.3.0 - - - src/main/assembly/dist.xml - - - - - lambda - package - - single - - - - - - - - diff --git a/samples/struts/pet-store/src/main/assembly/dist.xml b/samples/struts/pet-store/src/main/assembly/dist.xml deleted file mode 100644 index 029ec01c7..000000000 --- a/samples/struts/pet-store/src/main/assembly/dist.xml +++ /dev/null @@ -1,31 +0,0 @@ - - lambda - - zip - - false - - - lib - false - - - - - ${basedir}/src/main/resources - / - - * - - - - ${project.build.directory}/classes - / - - **/*.class - - - - \ No newline at end of file diff --git a/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/actions/PetsController.java b/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/actions/PetsController.java deleted file mode 100644 index bc2ad8bbe..000000000 --- a/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/actions/PetsController.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -package com.amazonaws.serverless.sample.struts.actions; - -import com.amazonaws.serverless.sample.struts.model.Pet; -import com.amazonaws.serverless.sample.struts.model.PetData; -import com.opensymphony.xwork2.ModelDriven; -import org.apache.struts2.rest.DefaultHttpHeaders; -import org.apache.struts2.rest.HttpHeaders; -import org.apache.struts2.rest.RestActionSupport; - -import java.util.Collection; -import java.util.UUID; -import java.util.stream.Collectors; - - -public class PetsController extends RestActionSupport implements ModelDriven { - - private Pet model = new Pet(); - private String id; - private Collection list = null; - - // GET /pets/1 - public HttpHeaders show() { - return new DefaultHttpHeaders("show"); - } - - // GET /pets - public HttpHeaders index() { - list = PetData.getNames() - .stream() - .map(petName -> new Pet( - UUID.randomUUID() - .toString(), PetData.getRandomBreed(), petName, PetData.getRandomDoB())) - .collect(Collectors.toList()); - return new DefaultHttpHeaders("index") - .disableCaching(); - } - - // POST /pets - public HttpHeaders create() { - if (model.getName() == null || model.getBreed() == null) { - return null; - } - - Pet dbPet = model; - dbPet.setId(UUID.randomUUID().toString()); - return new DefaultHttpHeaders("success") - .setLocationId(model.getId()); - - } - - // PUT /pets/1 - public String update() { - //TODO: UPDATE LOGIC - return SUCCESS; - } - - // DELETE /petsr/1 - public String destroy() { - //TODO: DELETE LOGIC - return SUCCESS; - } - - public void setId(String id) { - if (id != null) { - this.model = new Pet(id, PetData.getRandomBreed(), PetData.getRandomName(), PetData.getRandomDoB()); - } - this.id = id; - } - - public Object getModel() { - if (list != null) { - return list; - } else { - if (model == null) { - model = new Pet(); - } - return model; - } - } -} diff --git a/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/model/Pet.java b/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/model/Pet.java deleted file mode 100644 index c9d420ca8..000000000 --- a/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/model/Pet.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -package com.amazonaws.serverless.sample.struts.model; - -import org.hibernate.validator.constraints.NotBlank; - -import java.util.Date; - -public class Pet { - - private String id; - private String breed; - - @NotBlank - private String name; - private Date dateOfBirth; - - public Pet() { - } - - public Pet(String id, String breed, String name, Date dateOfBirth) { - this.id = id; - this.breed = breed; - this.name = name; - this.dateOfBirth = dateOfBirth; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getBreed() { - return breed; - } - - public void setBreed(String breed) { - this.breed = breed; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Date getDateOfBirth() { - return dateOfBirth; - } - - public void setDateOfBirth(Date dateOfBirth) { - this.dateOfBirth = dateOfBirth; - } -} diff --git a/samples/struts/pet-store/src/main/resources/log4j2.xml b/samples/struts/pet-store/src/main/resources/log4j2.xml deleted file mode 100644 index 55ed0d21c..000000000 --- a/samples/struts/pet-store/src/main/resources/log4j2.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n - - - - - - - - - - - \ No newline at end of file diff --git a/samples/struts/pet-store/src/main/resources/struts.xml b/samples/struts/pet-store/src/main/resources/struts.xml deleted file mode 100644 index 3f6724e50..000000000 --- a/samples/struts/pet-store/src/main/resources/struts.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/samples/struts/pet-store/test-event.json b/samples/struts/pet-store/test-event.json deleted file mode 100644 index 5860f3e86..000000000 --- a/samples/struts/pet-store/test-event.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "body": "eyJ0ZXN0IjoiYm9keSJ9", - "resource": "/{proxy+}", - "path": "/pets.json", - "httpMethod": "GET", - "isBase64Encoded": true, - "queryStringParameters": { - "foo": "bar" - }, - "multiValueQueryStringParameters": { - "foo": [ - "bar" - ] - }, - "pathParameters": { - "proxy": "/path/to/resource" - }, - "stageVariables": { - "baz": "qux" - }, - "headers": { - "Accept": "application/json", - "Accept-Encoding": "gzip, deflate, sdch", - "Accept-Language": "en-US,en;q=0.8", - "Cache-Control": "max-age=0", - "CloudFront-Forwarded-Proto": "https", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-Mobile-Viewer": "false", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Tablet-Viewer": "false", - "CloudFront-Viewer-Country": "US", - "Host": "1234567890.execute-api.us-east-1.amazonaws.com", - "Upgrade-Insecure-Requests": "1", - "User-Agent": "Custom User Agent String", - "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", - "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", - "X-Forwarded-For": "127.0.0.1, 127.0.0.2", - "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "multiValueHeaders": { - "Accept": [ - "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" - ], - "Accept-Encoding": [ - "gzip, deflate, sdch" - ], - "Accept-Language": [ - "en-US,en;q=0.8" - ], - "Cache-Control": [ - "max-age=0" - ], - "CloudFront-Forwarded-Proto": [ - "https" - ], - "CloudFront-Is-Desktop-Viewer": [ - "true" - ], - "CloudFront-Is-Mobile-Viewer": [ - "false" - ], - "CloudFront-Is-SmartTV-Viewer": [ - "false" - ], - "CloudFront-Is-Tablet-Viewer": [ - "false" - ], - "CloudFront-Viewer-Country": [ - "US" - ], - "Host": [ - "0123456789.execute-api.us-east-1.amazonaws.com" - ], - "Upgrade-Insecure-Requests": [ - "1" - ], - "User-Agent": [ - "Custom User Agent String" - ], - "Via": [ - "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)" - ], - "X-Amz-Cf-Id": [ - "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==" - ], - "X-Forwarded-For": [ - "127.0.0.1, 127.0.0.2" - ], - "X-Forwarded-Port": [ - "443" - ], - "X-Forwarded-Proto": [ - "https" - ] - }, - "requestContext": { - "accountId": "123456789012", - "resourceId": "123456", - "stage": "prod", - "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", - "requestTime": "09/Apr/2015:12:34:56 +0000", - "requestTimeEpoch": 1428582896000, - "identity": { - "cognitoIdentityPoolId": null, - "accountId": null, - "cognitoIdentityId": null, - "caller": null, - "accessKey": null, - "sourceIp": "127.0.0.1", - "cognitoAuthenticationType": null, - "cognitoAuthenticationProvider": null, - "userArn": null, - "userAgent": "Custom User Agent String", - "user": null - }, - "path": "/pets.json", - "resourcePath": "/{proxy+}", - "httpMethod": "GET", - "apiId": "1234567890", - "protocol": "HTTP/1.1" - } -} \ No newline at end of file