diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 14b1a472a..32e04baae 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,12 +1,30 @@ -* Framework version: XX -* Implementations: Jersey / Spring / Spring Boot / Spark +*To help us debug your issue fill in the basic information below using the options provided* + +*Serverless Java Container version*: `eg. 1.5` + +*Implementations:* `Jersey / Spring / Spring Boot / Spring Boot 2 / Spark` + +*Framework version:* `eg SpringBoot 2.2.6.RELEASE` + +*Frontend service:* `REST API / HTTP API / ALB` + +*Deployment method:* `eg SAM, Serverless Framework, Console` ## Scenario +*Describe what you are trying to accomplish* ## Expected behavior +*Describe how you would expect the application to behave* ## Actual behavior +*Describe what you are seeing instead* ## Steps to reproduce +*Provide code samples we can use to reproduce the issue as part of our integration tests. If there is a public repository for the misbehaving application link to it here* ## Full log output +*Paste the full log output from the Lambda function's CloudWatch logs* + +``` +logs +``` \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..379cffdb7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +*Issue #, if available:* + +*Description of changes:* + + +By submitting this pull request + +- [ ] I confirm that my contribution is made under the terms of the Apache 2.0 license. +- [ ] I confirm that I've made a best effort attempt to update all relevant documentation. \ No newline at end of file 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 new file mode 100644 index 000000000..08fe294d4 --- /dev/null +++ b/.github/workflows/continuous-integration-workflow.yml @@ -0,0 +1,100 @@ +name: Continuous Integration +on: + push: + pull_request: + branches: + - main + - 2.0.x + - 1.x + workflow_dispatch: + +permissions: + contents: read + +jobs: + build_core: + name: Build and test core + 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 + + build_jersey: + name: Build and test Jersey + 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 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 + 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 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_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 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 + +# 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 new file mode 100644 index 000000000..9faff653b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,40 @@ +name: Publish package to the Maven Central Repository +on: + workflow_dispatch: + inputs: + releaseVersion: + description: "Version to use for the release." + required: true + default: "X.Y.Z" + developmentVersion: + description: "Version to use for further development" + required: true + default: "X.Y.Z-SNAPSHOT" +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Configure Git User # may be removed after https://github.com/actions/checkout/issues/13 is resolved + run: | + git config user.email "${{ github.actor }}@users.noreply.github.com" + git config user.name "${{ github.actor }}" + - name: Set up Maven + uses: actions/setup-java@v3 + with: + distribution: 'corretto' + java-version: 17 + server-id: sonatype-nexus-staging + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.GPG_SIGNING_KEY }} # Value of the GPG private key to import + gpg-passphrase: GPG_PASSPHRASE # env variable for GPG private key passphrase + - name: Release and publish package + run: mvn release:prepare release:perform -B -DreleaseVersion=${{ github.event.inputs.releaseVersion }} -DdevelopmentVersion=${{ github.event.inputs.developmentVersion }} + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_JIRA_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_JIRA_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.gitignore b/.gitignore index 870a2d42f..2aa1654c2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ *.jar *.war *.ear +*.project +*.classpath +*.settings # Idea project files .idea/ @@ -29,3 +32,7 @@ gradlew* # Exclude maven wrapper !/.mvn/wrapper/maven-wrapper.jar + +# SAM files +samconfig.toml +.aws-sam/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f79cc86bd..000000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: java -dist: trusty -jdk: - - openjdk8 -addons: - apt: - update: true -before_install: - - wget https://services.gradle.org/distributions/gradle-5.0-bin.zip - - mkdir /opt/gradle - - unzip -d /opt/gradle gradle-5.0-bin.zip - - export GRADLE=/opt/gradle/gradle-5.0/bin/gradle -install: true -script: ./travis.sh 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 66ffc502a..3f26e0c1b 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,30 @@ -# Serverless Java container [![Build Status](https://travis-ci.org/awslabs/aws-serverless-java-container.svg?branch=master)](https://travis-ci.org/awslabs/aws-serverless-java-container) [![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 quick start](https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Spring-Boot) -* [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: -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). +| 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: | + +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 { - private static SpringLambdaContainerHandler handler; + private static final SpringLambdaContainerHandler handler; + static { try { handler = SpringLambdaContainerHandler.getAwsProxyHandler(PetStoreSpringAppConfig.class); @@ -32,3 +42,21 @@ 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 02a535a35..e9aa6bbec 100644 --- a/aws-serverless-java-container-core/pom.xml +++ b/aws-serverless-java-container-core/pom.xml @@ -6,97 +6,95 @@ 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.4-SNAPSHOT + 2.1.5-SNAPSHOT com.amazonaws.serverless aws-serverless-java-container - 1.4-SNAPSHOT + 2.1.5-SNAPSHOT .. - 2.9.8 - 2.1 - 3.1.0 + 3.1.0 + 6.0.0 - com.amazonaws aws-lambda-java-core - 1.2.0 + 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.6 - compile + org.apache.commons + commons-fileupload2-jakarta-servlet6 + 2.0.0-M4 - - org.apache.httpcomponents - httpclient - 4.5.6 + org.springframework.security + spring-security-web + 6.5.1 test - - org.apache.httpcomponents - httpcore - 4.4.10 - compile - + + 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 - 0.8.1 ${basedir}/target/coverage-reports/jacoco-unit.exec ${basedir}/target/coverage-reports/jacoco-unit.exec + + com/amazonaws/serverless/proxy/internal/testutils/** + @@ -137,29 +135,6 @@ com.github.spotbugs spotbugs-maven-plugin - 3.1.1 - - - Max - - Low - - true - - ${project.build.directory}/spotbugs - - - - com.h3xstream.findsecbugs - findsecbugs-plugin - 1.7.1 - - - org.glassfish.jersey.core jersey-server @@ -46,26 +53,29 @@ ${jersey.version} true test + + + jakarta.annotation + jakarta.annotation-api + + - commons-codec commons-codec - 1.10 + 1.18.0 test - com.fasterxml.jackson.core jackson-databind - 2.9.8 + ${jackson.version} true test - org.glassfish.jersey.media jersey-media-json-jackson @@ -94,6 +104,17 @@ ${jersey.version} test + + org.junit.jupiter + junit-jupiter + test + + + jakarta.ws.rs + jakarta.ws.rs-api + 3.1.0 + + @@ -101,7 +122,6 @@ org.jacoco jacoco-maven-plugin - 0.8.1 ${basedir}/target/coverage-reports/jacoco-unit.exec ${basedir}/target/coverage-reports/jacoco-unit.exec @@ -146,37 +166,13 @@ org.apache.maven.plugins maven-surefire-plugin - 2.9 - always + false com.github.spotbugs spotbugs-maven-plugin - 3.1.1 - - - Max - - Low - - true - - ${project.build.directory}/spotbugs - - - - com.h3xstream.findsecbugs - findsecbugs-plugin - 1.7.1 - - - - - com.amazonaws.serverless - aws-serverless-java-container-core - 1.4-SNAPSHOT - - - - - com.sparkjava - spark-core - ${spark.version} - - - - org.slf4j - slf4j-api - - - - - - - - - org.jacoco - jacoco-maven-plugin - 0.8.1 - - ${basedir}/target/coverage-reports/jacoco-unit.exec - ${basedir}/target/coverage-reports/jacoco-unit.exec - - - - default-prepare-agent - - prepare-agent - - - - jacoco-site - package - - report - - - - jacoco-check - test - - check - - - true - - BUNDLE - - - INSTRUCTION - COVEREDRATIO - ${jacoco.minCoverage} - - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.9 - - always - - - - com.github.spotbugs - spotbugs-maven-plugin - 3.1.1 - - - Max - - Low - - true - - ${project.build.directory}/spotbugs - - - - com.h3xstream.findsecbugs - findsecbugs-plugin - 1.7.1 - - - - - - - analyze-compile - compile - - check - - - - - - org.owasp - dependency-check-maven - ${dependencyCheck.version} - - true - - ${project.basedir}/../owasp-suppression.xml - - 7 - - - - - - check - - - - - - - - diff --git a/aws-serverless-java-container-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 247140413..000000000 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java +++ /dev/null @@ -1,220 +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.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.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.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 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; - } - - //------------------------------------------------------------- - // 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(AwsProxyHttpServletRequest request, CountDownLatch latch) { - return new AwsHttpServletResponse(request, latch); - } - - - @Override - protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext) - throws Exception { - Timer.start("SPARK_HANDLE_REQUEST"); - - if (embeddedServer == null) { - initialize(); - } - - 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), true, "/*"); - Timer.stop("SPARK_COLD_START"); - } -} 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 f4f227c4e..000000000 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServer.java +++ /dev/null @@ -1,107 +0,0 @@ -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 3455170fa..000000000 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerFactory.java +++ /dev/null @@ -1,60 +0,0 @@ -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 74cd55040..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkStreamTest.java +++ /dev/null @@ -1,143 +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 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 java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -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 boolean isAlb; - - public HelloWorldSparkStreamTest(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 helloRequest_basicStream_populatesOutputSuccessfully() { - InputStream req = getRequestBuilder().method("GET").path("/hello").buildStream(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try { - handler.proxyStream(req, outputStream, 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() { - InputStream req = getRequestBuilder().method("GET").path(null).buildStream(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try { - handler.proxyStream(req, outputStream, 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 4e09a35dd..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java +++ /dev/null @@ -1,158 +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 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()); - } - - 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; - }); - } -} 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 fb39d9b69..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java +++ /dev/null @@ -1,79 +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) -> { - System.out.println("Exception Handler called: " + e.getLocalizedMessage()); - 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 400373a40..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java +++ /dev/null @@ -1,126 +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) { - System.out.println("Null servlet context"); - 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) { - System.out.println("Null servlet context"); - 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 8a0e1f4eb..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(0)); - } 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 349994456..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/CustomHeaderFilter.java +++ /dev/null @@ -1,38 +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 { - System.out.println("Called init on filter"); - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - System.out.println("Called doFilter"); - HttpServletResponse resp = (HttpServletResponse)servletResponse; - resp.addHeader(HEADER_NAME, HEADER_VALUE); - - filterChain.doFilter(servletRequest, servletResponse); - } - - - @Override - public void destroy() { - System.out.println("Called destroy"); - } -} \ No newline at end of file diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java deleted file mode 100644 index 60e8eb429..000000000 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java +++ /dev/null @@ -1,45 +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 { - System.out.println("Running unauth filter"); - if (((HttpServletRequest)servletRequest).getHeader(HEADER_NAME) != null) { - ((HttpServletResponse) servletResponse).setStatus(401); - System.out.println("Returning 401"); - return; - } - System.out.println("Continue chain"); - filterChain.doFilter(servletRequest, servletResponse); - } - - - @Override - public void destroy() { - - } -} diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index 22ce578bd..f03d9db0a 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -6,20 +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.4-SNAPSHOT + 2.1.5-SNAPSHOT com.amazonaws.serverless aws-serverless-java-container - 1.4-SNAPSHOT + 2.1.5-SNAPSHOT .. - 5.1.1.RELEASE - 1.5.17.RELEASE - 5.1.1.RELEASE - 2.9.8 + 6.2.8 + 6.5.1 @@ -27,10 +25,17 @@ com.amazonaws.serverless aws-serverless-java-container-core - 1.4-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 @@ -38,7 +43,6 @@ true - org.springframework spring-test @@ -46,11 +50,10 @@ test - commons-codec commons-codec - 1.10 + 1.18.0 test @@ -62,41 +65,32 @@ - org.hibernate - hibernate-validator - 5.4.1.Final + jakarta.activation + jakarta.activation-api + 2.1.3 test + - javax.el - javax.el-api - 2.2.4 + org.hibernate.validator + hibernate-validator + 8.0.2.Final test - - org.glassfish.web - javax.el - 2.2.4 + org.junit.jupiter + junit-jupiter test + - org.springframework.boot - spring-boot-autoconfigure - ${springboot.version} + org.glassfish.expressly + expressly + 5.0.0 test - - - org.springframework - spring-context - - - org.springframework - spring-core - - + org.springframework.security spring-security-config @@ -164,7 +158,6 @@ org.jacoco jacoco-maven-plugin - 0.8.1 ${basedir}/target/coverage-reports/jacoco-unit.exec ${basedir}/target/coverage-reports/jacoco-unit.exec @@ -209,37 +202,13 @@ org.apache.maven.plugins maven-surefire-plugin - 2.9 - always + false com.github.spotbugs spotbugs-maven-plugin - 3.1.1 - - - Max - - Low - - true - - ${project.build.directory}/spotbugs - - - - com.h3xstream.findsecbugs - findsecbugs-plugin - 1.7.1 - - - + + 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 + 2.1.5-SNAPSHOT + tests + test-jar + test + + + org.springframework + spring-webflux + ${spring.version} + true + + + org.springframework.boot + spring-boot + ${springboot.version} + true + + + org.springframework + spring-context + + + org.springframework + spring-core + + + + + org.springframework.boot + spring-boot-autoconfigure + ${springboot.version} + true + + + org.springframework + spring-core + ${spring.version} + true + + + org.springframework + spring-context + ${spring.version} + true + + + org.springframework + spring-webmvc + ${spring.version} + true + + + org.springframework + spring-aop + + + org.springframework + spring-expression + + + + + + org.springframework.security + spring-security-config + ${springsecurity.version} + + + org.springframework + spring-context + + + org.springframework + spring-beans + + + org.springframework + spring-core + + + org.springframework + spring-expression + + + org.springframework + spring-aop + + + test + + + org.springframework.security + spring-security-web + ${springsecurity.version} + + + org.springframework + spring-core + + + org.springframework + spring-web + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + org.springframework + spring-expression + + + org.springframework + spring-aop + + + test + + + org.hibernate.validator + hibernate-validator + 8.0.2.Final + test + + + + org.junit.jupiter + junit-jupiter + test + + + + jakarta.validation + jakarta.validation-api + 3.1.1 + test + + + + 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 + + + + + com.h2database + h2 + 2.3.232 + test + + + + + + + + + org.jacoco + jacoco-maven-plugin + + ${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* + + + + + 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 + + false + + + + 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 + + + + 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-springboot3/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 new file mode 100644 index 000000000..45bf28efe --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -0,0 +1,236 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.proxy.spring; + +import 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.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; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; +import com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveServletEmbeddedServerFactory; +import com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory; +import com.amazonaws.services.lambda.runtime.Context; + +import jakarta.servlet.Servlet; +import jakarta.servlet.http.HttpServletRequest; + +/** + * SpringBoot implementation of the `LambdaContainerHandler` abstract class. This class uses the `LambdaSpringApplicationInitializer` + * object behind the scenes to proxy requests. The default implementation leverages the `AwsProxyHttpServletRequest` and + * `AwsHttpServletResponse` implemented in the `aws-serverless-java-container-core` package. + * + * Important: Make sure to add LambdaFlushResponseListener in your SpringBootServletInitializer subclass configure(). + * + * @param The incoming event type + * @param The expected return type + */ +public class SpringBootLambdaContainerHandler extends AwsLambdaServletContainerHandler { + private static final String DISPATCHER_SERVLET_REGISTRATION_NAME = "dispatcherServlet"; + + private final Class springBootInitializer; + private static final Logger log = LoggerFactory.getLogger(SpringBootLambdaContainerHandler.class); + private String[] springProfiles = null; + private WebApplicationType springWebApplicationType; + private ConfigurableApplicationContext applicationContext; + + private static SpringBootLambdaContainerHandler instance; + + // State vars + private boolean initialized; + + /** + * We need to rely on the static instance of this for SpringBoot because we need it to access the ServletContext. + * Normally, SpringBoot would initialize its own embedded container through the SpringApplication.run() + * method. However, in our case we need to rely on the pre-initialized handler and need to fetch information from it + * for our mock {@link ServerlessReactiveServletEmbeddedServerFactory}. + * + * @return The initialized instance + */ + public static SpringBootLambdaContainerHandler getInstance() { + return instance; + } + + /** + * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects and the given Spring profiles + * @param springBootInitializer {@code SpringBootServletInitializer} class + * @param profiles A list of Spring profiles to activate + * @return An initialized instance of the `SpringLambdaContainerHandler` + * @throws ContainerInitializationException If an error occurs while initializing the Spring framework + */ + public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class springBootInitializer, String... profiles) + throws ContainerInitializationException { + return new SpringBootProxyHandlerBuilder() + .defaultProxy() + .initializationWrapper(new InitializationWrapper()) + .springBootApplication(springBootInitializer) + .profiles(profiles) + .buildAndInitialize(); + } + + /** + * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `HttpApiV2ProxyRequest` objects and the given Spring profiles + * @param springBootInitializer {@code SpringBootServletInitializer} class + * @param profiles A list of Spring profiles to activate + * @return An initialized instance of the `SpringLambdaContainerHandler` + * @throws ContainerInitializationException If an error occurs while initializing the Spring framework + */ + public static SpringBootLambdaContainerHandler getHttpApiV2ProxyHandler(Class springBootInitializer, String... profiles) + throws ContainerInitializationException { + return new SpringBootProxyHandlerBuilder() + .defaultHttpApiV2Proxy() + .initializationWrapper(new InitializationWrapper()) + .springBootApplication(springBootInitializer) + .profiles(profiles) + .buildAndInitialize(); + } + + /** + * Creates a new container handler with the given reader and writer objects + * + * @param requestTypeClass The class for the incoming Lambda event + * @param responseTypeClass The class for the Lambda function output + * @param requestReader An implementation of `RequestReader` + * @param responseWriter An implementation of `ResponseWriter` + * @param securityContextWriter An implementation of `SecurityContextWriter` + * @param exceptionHandler An implementation of `ExceptionHandler` + * @param springBootInitializer {@code SpringBootServletInitializer} class + * @param init The initialization Wrapper that will be used to start Spring Boot + * @param applicationType The Spring Web Application Type + */ + public SpringBootLambdaContainerHandler(Class requestTypeClass, + Class responseTypeClass, + RequestReader requestReader, + ResponseWriter responseWriter, + SecurityContextWriter securityContextWriter, + ExceptionHandler exceptionHandler, + Class springBootInitializer, + InitializationWrapper init, + WebApplicationType applicationType) { + super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); + Timer.start("SPRINGBOOT2_CONTAINER_HANDLER_CONSTRUCTOR"); + initialized = false; + this.springBootInitializer = springBootInitializer; + springWebApplicationType = applicationType; + setInitializationWrapper(init); + SpringBootLambdaContainerHandler.setInstance(this); + + Timer.stop("SPRINGBOOT2_CONTAINER_HANDLER_CONSTRUCTOR"); + } + + // this is not pretty. However, because SpringBoot wants to control all of the initialization + // we need to access this handler as a singleton from the EmbeddedContainer to set the servlet + // context and from the ServletConfigurationSupport implementation + private static void setInstance(SpringBootLambdaContainerHandler h) { + SpringBootLambdaContainerHandler.instance = h; + } + + public void activateSpringProfiles(String... profiles) { + springProfiles = profiles; + // force a re-initialization + initialized = false; + } + + @Override + protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) { + return new AwsHttpServletResponse(request, latch); + } + + @Override + protected void handleRequest(HttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + // this method of the AwsLambdaServletContainerHandler sets the servlet context + Timer.start("SPRINGBOOT2_HANDLE_REQUEST"); + + // wire up the application context on the first invocation + if (!initialized) { + initialize(); + } + + // process filters & invoke servlet + Servlet reqServlet = ((AwsServletContext)getServletContext()).getServletForPath(containerRequest.getPathInfo()); + if (AwsHttpServletRequest.class.isAssignableFrom(containerRequest.getClass())) { + ((AwsHttpServletRequest)containerRequest).setServletContext(getServletContext()); + ((AwsHttpServletRequest)containerRequest).setResponse(containerResponse); + } + doFilter(containerRequest, containerResponse, reqServlet); + 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 = 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) { + ((ConfigurableWebApplicationContext)applicationContext).setServletContext(getServletContext()); + AwsServletRegistration reg = (AwsServletRegistration)getServletContext().getServletRegistration(DISPATCHER_SERVLET_REGISTRATION_NAME); + if (reg != null) { + reg.setLoadOnStartup(1); + } + } + super.initialize(); + initialized = true; + Timer.stop("SPRINGBOOT2_COLD_START"); + } + + private Class[] getEmbeddedContainerClasses() { + Class[] classes = new Class[2]; + if (springWebApplicationType == WebApplicationType.REACTIVE) { + try { + // if HandlerAdapter is available we assume they are using WebFlux. Otherwise plain servlet. + this.getClass().getClassLoader().loadClass("org.springframework.web.reactive.HandlerAdapter"); + log.debug("Found WebFlux HandlerAdapter on classpath, using reactive server factory"); + classes[0] = ServerlessReactiveServletEmbeddedServerFactory.class; + } catch (ClassNotFoundException e) { + springWebApplicationType = WebApplicationType.SERVLET; + classes[0] = ServerlessServletEmbeddedServerFactory.class; + } + } else { + classes[0] = ServerlessServletEmbeddedServerFactory.class; + } + + classes[1] = springBootInitializer; + return classes; + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..e7ad017f1 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.ExceptionHandler; +import com.amazonaws.serverless.proxy.internal.servlet.ServletLambdaContainerHandlerBuilder; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import org.springframework.boot.WebApplicationType; + +import jakarta.servlet.http.HttpServletRequest; + +public final class SpringBootProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< + RequestType, + AwsProxyResponse, + HttpServletRequest, + SpringBootLambdaContainerHandler, + SpringBootProxyHandlerBuilder> { + private Class springBootInitializer; + private String[] profiles; + private WebApplicationType applicationType = WebApplicationType.REACTIVE; + + @Override + protected SpringBootProxyHandlerBuilder self() { + return this; + } + + + public SpringBootProxyHandlerBuilder springBootApplication(Class app) { + springBootInitializer = app; + return self(); + } + + public SpringBootProxyHandlerBuilder profiles(String... profiles) { + this.profiles = profiles; + return self(); + } + + public SpringBootProxyHandlerBuilder servletApplication() { + this.applicationType = WebApplicationType.SERVLET; + return self(); + } + + @Override + public SpringBootLambdaContainerHandler build() throws ContainerInitializationException { + validate(); + if (springBootInitializer == null) { + throw new ContainerInitializationException("Missing spring boot application class in builder", null); + } + SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler( + requestTypeClass, + responseTypeClass, + requestReader, + responseWriter, + securityContextWriter, + exceptionHandler, + springBootInitializer, + initializationWrapper, + applicationType + ); + if (profiles != null) { + handler.activateSpringProfiles(profiles); + } + return handler; + } + + @Override + public SpringBootLambdaContainerHandler buildAndInitialize() throws ContainerInitializationException { + SpringBootLambdaContainerHandler handler = build(); + 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-springboot3/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 new file mode 100644 index 000000000..3376452e2 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java @@ -0,0 +1,105 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.proxy.spring.embedded; + +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory; +import org.springframework.boot.web.server.WebServer; +import org.springframework.boot.web.server.WebServerException; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; + +import jakarta.servlet.*; +import java.io.IOException; +import java.util.Enumeration; + +@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) +public class ServerlessReactiveServletEmbeddedServerFactory extends AbstractReactiveWebServerFactory implements WebServer, Servlet { + private ServletHttpHandlerAdapter handler; + private ServletConfig config; + static final String SERVLET_NAME = "com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveEmbeddedServerFactory"; + static final String SERVLET_INFO = "ServerlessReactiveEmbeddedServerFactory"; + + @Override + @SuppressFBWarnings("MTIA_SUSPECT_SERVLET_INSTANCE_FIELD") + public WebServer getWebServer(HttpHandler httpHandler) { + handler = new ServletHttpHandlerAdapter(httpHandler); + return this; + } + + @Override + public void start() throws WebServerException { + // register this object as the main handler servlet with a mapping of / + SpringBootLambdaContainerHandler + .getInstance() + .getServletContext() + .addServlet(SERVLET_NAME, this) + .addMapping("/"); + handler.init(new ServletAdapterConfig()); + } + + @Override + public void stop() throws WebServerException { + // nothing to do here. + } + + @Override + public void init(ServletConfig servletConfig) throws ServletException { + config = servletConfig; + } + + @Override + public ServletConfig getServletConfig() { + return config; + } + + @Override + public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { + handler.service(servletRequest, servletResponse); + } + + @Override + public String getServletInfo() { + return SERVLET_INFO; + } + + @Override + public void destroy() { + + } + + private static class ServletAdapterConfig implements ServletConfig { + @Override + public String getServletName() { + return SERVLET_NAME; + } + + @Override + public ServletContext getServletContext() { + return SpringBootLambdaContainerHandler.getInstance().getServletContext(); + } + + @Override + public String getInitParameter(String s) { + return null; + } + + @Override + public Enumeration getInitParameterNames() { + return null; + } + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..76ee919dc --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.proxy.spring.embedded; + +import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.web.server.WebServer; +import org.springframework.boot.web.server.WebServerException; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; +import org.springframework.core.Ordered; + +import jakarta.servlet.ServletException; + +@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) +public class ServerlessServletEmbeddedServerFactory implements ServletWebServerFactory, WebServer { + @SuppressWarnings("rawtypes") + private AwsLambdaServletContainerHandler handler; + + public ServerlessServletEmbeddedServerFactory() { + super(); + handler = SpringBootLambdaContainerHandler.getInstance(); + } + + @Override + public WebServer getWebServer(ServletContextInitializer... initializers) { + for (ServletContextInitializer i : initializers) { + try { + if (handler.getServletContext() == null) { + throw new WebServerException("Attempting to initialize ServletEmbeddedWebServer without ServletContext in Handler", null); + } + i.onStartup(handler.getServletContext()); + } catch (ServletException e) { + throw new WebServerException("Could not initialize Servlets", e); + } + } + return this; + } + + @Override + public void start() throws WebServerException { + + } + + @Override + public void stop() throws WebServerException { + + } + + @Override + public int getPort() { + return 0; + } +} diff --git a/aws-serverless-java-container-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-springboot3/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 new file mode 100644 index 000000000..d0b579509 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SecurityAppTest.java @@ -0,0 +1,39 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.securityapp.LambdaHandler; +import com.amazonaws.serverless.proxy.spring.securityapp.SecurityConfig; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SecurityAppTest { + + LambdaHandler handler = new LambdaHandler(); + MockLambdaContext lambdaContext = new MockLambdaContext(); + + public SecurityAppTest() { + System.setProperty("logging.level.root", "DEBUG"); + } + + @Test + void helloRequest_withAuth_respondsWithSingleMessage() { + AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertEquals(401, resp.getStatusCode()); + assertTrue(resp.getMultiValueHeaders().containsKey(HttpHeaders.WWW_AUTHENTICATE)); + req = new AwsProxyRequestBuilder("/hello", "GET") + .basicAuth(SecurityConfig.USERNAME, SecurityConfig.PASSWORD) + .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN) + .build(); + resp = handler.handleRequest(req, lambdaContext); + assertEquals(200, resp.getStatusCode()); + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..b87c80ce6 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java @@ -0,0 +1,236 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.model.ContainerConfig; +import com.amazonaws.serverless.proxy.spring.servletapp.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +public class ServletAppTest { + + 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 initServletAppTest(String reqType) { + type = reqType; + handler = new LambdaHandler(type); + } + + @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); + assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + } + + @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) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .body(ud); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + try { + System.out.println(LambdaContainerHandler.getObjectMapper().writeValueAsString(resp)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + assertEquals("3", resp.getBody()); + assertEquals(400, resp.getStatusCode()); + + UserData ud2 = new UserData(); + ud2.setFirstName("Test"); + ud2.setLastName("Test"); + ud2.setEmail("Test"); + req = new AwsProxyRequestBuilder("/validate", "POST") + .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .body(ud2); + resp = handler.handleRequest(req, lambdaContext); + assertEquals("1", resp.getBody()); + assertEquals(400, resp.getStatusCode()); + } + + @MethodSource("data") + @ParameterizedTest + void messageObject_parsesObject_returnsCorrectMessage(String reqType) { + initServletAppTest(reqType); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/message", "POST") + .json() + .body(new MessageData("test message")); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + assertEquals("test message", resp.getBody()); + } + + @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") + .body(new MessageData("test message")); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + assertEquals("test message", resp.getBody()); + } + + @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); + assertEquals(200, resp.getStatusCode()); + assertEquals("test.test.test", resp.getBody()); + } + + @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); + AwsProxyRequestBuilder req = new AwsProxyRequestBuilder("/content-type/utf8", "GET") + .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + assertEquals("text/plain; charset=UTF-8", resp.getMultiValueHeaders().get(HttpHeaders.CONTENT_TYPE).stream().collect(Collectors.joining(","))); + assertEquals(MessageController.UTF8_RESPONSE, resp.getBody()); + } + + @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()); + } + + @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") + .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN); + InputStream req = null; + switch (type) { + case "ALB": + req = reqBuilder.alb().buildStream(); + break; + case "API_GW": + req = reqBuilder.buildStream(); + break; + case "HTTP_API": + req = reqBuilder.toHttpApiV2RequestStream(); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + streamHandler.handleRequest(req, out, lambdaContext); + AwsProxyResponse resp = LambdaContainerHandler.getObjectMapper().readValue(out.toByteArray(), AwsProxyResponse.class); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + assertEquals(MessageController.UTF8_RESPONSE, resp.getBody()); + } + + @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"); + InputStream req = null; + switch (type) { + case "ALB": + req = reqBuilder.alb().buildStream(); + break; + case "API_GW": + req = reqBuilder.buildStream(); + break; + case "HTTP_API": + req = reqBuilder.toHttpApiV2RequestStream(); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + streamHandler.handleRequest(req, out, lambdaContext); + AwsProxyResponse resp = LambdaContainerHandler.getObjectMapper().readValue(out.toByteArray(), AwsProxyResponse.class); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + assertEquals("{\"s\":\"" + MessageController.UTF8_RESPONSE + "\"}", resp.getBody()); + } + + @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()); + } + + @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") + .body(new MessageData("test message")); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + if ("HTTP_API".equals(type)) { + assertNotNull(resp.getHeaders()); + } else { + assertNull(resp.getHeaders()); + } + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..f5e83e85e --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java @@ -0,0 +1,32 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.slowapp.LambdaHandler; +import com.amazonaws.serverless.proxy.spring.slowapp.MessageController; +import com.amazonaws.serverless.proxy.spring.slowapp.SlowTestApplication; +import org.junit.jupiter.api.Test; + +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SlowAppTest { + + @Test + void slowAppInit_continuesInBackgroundThread_returnsCorrect() { + LambdaHandler slowApp = new LambdaHandler(); + System.out.println("Start time: " + slowApp.getConstructorTime()); + assertTrue(slowApp.getConstructorTime() < 10_000); + AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); + long startRequestTime = Instant.now().toEpochMilli(); + AwsProxyResponse resp = slowApp.handleRequest(req, new MockLambdaContext()); + long endRequestTime = Instant.now().toEpochMilli(); + assertTrue(endRequestTime - startRequestTime > SlowTestApplication.SlowDownInit.INIT_SLEEP_TIME_MS - 10_000); + assertEquals(200, resp.getStatusCode()); + 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-springboot3/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 new file mode 100644 index 000000000..9c39fd905 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java @@ -0,0 +1,68 @@ +package com.amazonaws.serverless.proxy.spring; + +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.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; + +public class WebFluxAppTest { + + 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 initWebFluxAppTest(String reqType) { + type = reqType; + handler = new LambdaHandler(type); + } + + @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()); + assertEquals(MessageController.MESSAGE, resp.getBody()); + } + + @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()); + } + + @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")); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + assertEquals("test message", resp.getBody()); + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..5ffd4a311 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java @@ -0,0 +1,49 @@ +package com.amazonaws.serverless.proxy.spring.embedded; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; +import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; +import com.amazonaws.serverless.proxy.InitializationWrapper; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import org.junit.jupiter.api.Test; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.web.servlet.ServletContextInitializer; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; + +import static org.junit.jupiter.api.Assertions.fail; + +public class ServerlessServletEmbeddedServerFactoryTest { + private SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler<>( + AwsProxyRequest.class, + AwsProxyResponse.class, + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + null, + new InitializationWrapper(), + WebApplicationType.REACTIVE + ); + + public ServerlessServletEmbeddedServerFactoryTest() throws ContainerInitializationException { + } + + @Test + void getWebServer_callsInitializers() { + ServerlessServletEmbeddedServerFactory factory = new ServerlessServletEmbeddedServerFactory(); + factory.getWebServer(new ServletContextInitializer() { + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + if (servletContext == null) { + fail("Null servlet context"); + } + } + }); + } +} 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-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/JpaApplication.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/JpaApplication.java new file mode 100644 index 000000000..5aced5e28 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/jpaapp/JpaApplication.java @@ -0,0 +1,17 @@ +package com.amazonaws.serverless.proxy.spring.jpaapp; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.autoconfigure.SpringBootApplication; +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 = { + 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 +}) +@Import(MessageController.class) +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-springboot3/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 new file mode 100644 index 000000000..ae8ba21ac --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/LambdaHandler.java @@ -0,0 +1,25 @@ +package com.amazonaws.serverless.proxy.spring.securityapp; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +public class LambdaHandler implements RequestHandler { + private static SpringBootLambdaContainerHandler handler; + + static { + try { + handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(SecurityApplication.class); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + } + } + + @Override + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + return handler.proxy(awsProxyRequest, context); + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..ad67d4766 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/MessageController.java @@ -0,0 +1,17 @@ +package com.amazonaws.serverless.proxy.spring.securityapp; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + + +@RestController +public class MessageController { + public static final String HELLO_MESSAGE = "Hello"; + + @RequestMapping(path="/hello", method=RequestMethod.GET, produces = {"text/plain"}) + public Mono hello() { + return Mono.just(HELLO_MESSAGE); + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..d4036dcfe --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityApplication.java @@ -0,0 +1,17 @@ +package com.amazonaws.serverless.proxy.spring.securityapp; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.web.reactive.config.EnableWebFlux; + +@SpringBootApplication(exclude = { + org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class, + org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.class +}) +@EnableWebFluxSecurity +@EnableWebFlux +@Import(SecurityConfig.class) +public class SecurityApplication { +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..d83b81db6 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityConfig.java @@ -0,0 +1,44 @@ +package com.amazonaws.serverless.proxy.spring.securityapp; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.server.SecurityWebFilterChain; + +@Configuration +@EnableWebFluxSecurity +public class SecurityConfig +{ + public static final String USERNAME = "admin"; + public static final String PASSWORD = "{noop}password"; + private static BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + @Bean + public SecurityWebFilterChain securitygWebFilterChain( + ServerHttpSecurity http) { + return http.authorizeExchange() + .anyExchange().authenticated().and().csrf().disable() + .httpBasic() + .and().build(); + } + + @Bean + public static BCryptPasswordEncoder passwordEncoder() { + return passwordEncoder; + } + + @Bean + public MapReactiveUserDetailsService userDetailsService() { + UserDetails user = User + .withUsername(USERNAME) + .password(passwordEncoder.encode(PASSWORD)) + .roles("USER") + .build(); + return new MapReactiveUserDetailsService(user); + } +} \ No newline at end of file diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..88441988e --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java @@ -0,0 +1,60 @@ +package com.amazonaws.serverless.proxy.spring.servletapp; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.InitializationWrapper; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.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(ServletApplication.class) + .buildAndInitialize(); + break; + case "HTTP_API": + httpApiHandler = new SpringBootProxyHandlerBuilder() + .defaultHttpApiV2Proxy() + .initializationWrapper(new InitializationWrapper()) + .servletApplication() + .springBootApplication(ServletApplication.class) + .buildAndInitialize(); + break; + } + } catch (ContainerInitializationException e) { + e.printStackTrace(); + } + } + + @Override + public AwsProxyResponse handleRequest(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/servletapp/LambdaStreamHandler.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaStreamHandler.java new file mode 100644 index 000000000..fd7d71d79 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaStreamHandler.java @@ -0,0 +1,63 @@ +package com.amazonaws.serverless.proxy.spring.servletapp; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.InitializationWrapper; +import com.amazonaws.serverless.proxy.internal.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; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class LambdaStreamHandler implements RequestStreamHandler { + private static SpringBootLambdaContainerHandler handler; + private static SpringBootLambdaContainerHandler httpApiHandler; + private String type; + + public LambdaStreamHandler(String reqType) { + type = reqType; + try { + switch (type) { + case "API_GW": + case "ALB": + handler = new SpringBootProxyHandlerBuilder() + .defaultProxy() + .initializationWrapper(new InitializationWrapper()) + .servletApplication() + .springBootApplication(ServletApplication.class) + .buildAndInitialize(); + break; + case "HTTP_API": + httpApiHandler = new SpringBootProxyHandlerBuilder() + .defaultHttpApiV2Proxy() + .initializationWrapper(new InitializationWrapper()) + .servletApplication() + .springBootApplication(ServletApplication.class) + .buildAndInitialize(); + break; + } + } catch (ContainerInitializationException e) { + e.printStackTrace(); + } + } + + @Override + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + switch (type) { + case "API_GW": + case "ALB": + handler.proxyStream(inputStream, outputStream, context); + break; + case "HTTP_API": + httpApiHandler.proxyStream(inputStream, outputStream, context); + } + + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..1923396c6 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java @@ -0,0 +1,75 @@ +package com.amazonaws.serverless.proxy.spring.servletapp; + +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +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 jakarta.validation.Valid; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@RestController +public class MessageController { + public static final String HELLO_MESSAGE = "Hello"; + public static final String VALID_MESSAGE = "VALID"; + 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; + } + + @RequestMapping(path="/validate", method=RequestMethod.POST, produces = {"text/plain"}) + public ResponseEntity validateBody(@RequestBody @Valid UserData userData, Errors errors) { + if (errors != null && errors.hasErrors()) { + return ResponseEntity.badRequest().body(errors.getErrorCount() + ""); + } + return ResponseEntity.ok(VALID_MESSAGE); + } + + @RequestMapping(path="/message", method = RequestMethod.POST) + public String returnMessage(@RequestBody MessageData data) { + if (data == null) { + throw new RuntimeException("No message data"); + } + return data.getMessage(); + } + + @RequestMapping(path="/echo/{message}", method=RequestMethod.GET) + public String returnPathMessage(@PathVariable(value="message") String message) { + return message; + } + + @GetMapping(value = "/content-type/utf8", produces = "text/plain") + public ResponseEntity getUtf8String() { + return ResponseEntity.ok(UTF8_RESPONSE); + } + + @GetMapping(value = "/content-type/jsonutf8", produces=MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> getUtf8Json() { + Map resp = new HashMap(); + resp.put("s", UTF8_RESPONSE); + return ResponseEntity.ok(resp); + } + + @GetMapping(value = "/ex/customstatus") + public String throw404Exception() { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, EX_MESSAGE); + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..129101cbe --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageData.java @@ -0,0 +1,20 @@ +package com.amazonaws.serverless.proxy.spring.servletapp; + +public class MessageData { + private String message; + + public MessageData() { + } + + public MessageData(String m) { + setMessage(m); + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/aws-serverless-java-container-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-springboot3/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 new file mode 100644 index 000000000..379291a39 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/UserData.java @@ -0,0 +1,50 @@ +package com.amazonaws.serverless.proxy.spring.servletapp; + + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public class UserData { + @NotBlank + private String firstName; + @NotBlank + private String lastName; + @NotNull @Email + private String email; + private String error; + + public UserData() { + + } + + public UserData(String err) { + error = err; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getError() { return error; } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..22f75e7a9 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/LambdaHandler.java @@ -0,0 +1,40 @@ +package com.amazonaws.serverless.proxy.spring.slowapp; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.serverless.proxy.spring.SpringBootProxyHandlerBuilder; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import java.time.Instant; + +public class LambdaHandler implements RequestHandler { + private SpringBootLambdaContainerHandler handler; + private long constructorTime; + + public LambdaHandler() { + try { + long startTime = Instant.now().toEpochMilli(); + System.out.println("startCall: " + startTime); + handler = new SpringBootProxyHandlerBuilder() + .defaultProxy() + .springBootApplication(SlowTestApplication.class) + .buildAndInitialize(); + constructorTime = Instant.now().toEpochMilli() - startTime; + } catch (ContainerInitializationException e) { + e.printStackTrace(); + } + } + + public long getConstructorTime() { + return constructorTime; + } + + @Override + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + return handler.proxy(awsProxyRequest, context); + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..098e8e7df --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/MessageController.java @@ -0,0 +1,15 @@ +package com.amazonaws.serverless.proxy.spring.slowapp; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class MessageController { + public static final String HELLO_MESSAGE = "Hello"; + + @RequestMapping(path="/hello", method= RequestMethod.GET) + public String hello() { + return HELLO_MESSAGE; + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..006e51e45 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/SlowTestApplication.java @@ -0,0 +1,26 @@ +package com.amazonaws.serverless.proxy.spring.slowapp; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.stereotype.Component; + +import java.time.Instant; + +@SpringBootApplication(exclude = { + org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.class, + org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class, + org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.class +}) +public class SlowTestApplication { + + @Component + public static class SlowDownInit implements InitializingBean { + public static final int INIT_SLEEP_TIME_MS = 13_000; + + @Override + public void afterPropertiesSet() throws Exception { + Thread.sleep(INIT_SLEEP_TIME_MS); + } + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..0eb52a7bc --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/LambdaHandler.java @@ -0,0 +1,58 @@ +package com.amazonaws.serverless.proxy.spring.webfluxapp; + +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()) + .springBootApplication(WebFluxTestApplication.class) + .buildAndInitialize(); + break; + case "HTTP_API": + httpApiHandler = new SpringBootProxyHandlerBuilder() + .defaultHttpApiV2Proxy() + .initializationWrapper(new InitializationWrapper()) + .springBootApplication(WebFluxTestApplication.class) + .buildAndInitialize(); + break; + } + } catch (ContainerInitializationException e) { + e.printStackTrace(); + } + } + + @Override + public AwsProxyResponse handleRequest(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/webfluxapp/MessageController.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java new file mode 100644 index 000000000..a04604618 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java @@ -0,0 +1,36 @@ +package com.amazonaws.serverless.proxy.spring.webfluxapp; + +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import reactor.core.publisher.Flux; + +@RestController +public class MessageController { + public static final String MESSAGE = "Hello"; + + @RequestMapping(path="/single", method= RequestMethod.GET, produces = {"text/plain"}) + Flux singleMessage(){ + return Flux.just( + MESSAGE + ); + } + + @RequestMapping(path="/double", method= RequestMethod.GET, produces={"text/plain"}) + Flux doubleMessage(){ + return Flux.just( + MESSAGE, + MESSAGE + ); + } + + @RequestMapping(path="/message", method = RequestMethod.POST, produces={"text/plain"}, consumes = {"application/json"}) + public Flux returnMessage(@RequestBody MessageData data) { + if (data == null) { + throw new RuntimeException("No message data"); + } + return Flux.just(data.getMessage()); + } +} \ No newline at end of file diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..2be6b4f2d --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageData.java @@ -0,0 +1,20 @@ +package com.amazonaws.serverless.proxy.spring.webfluxapp; + +public class MessageData { + private String message; + + public MessageData() { + } + + public MessageData(String m) { + setMessage(m); + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/aws-serverless-java-container-springboot3/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 new file mode 100644 index 000000000..fc6aecd6f --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java @@ -0,0 +1,22 @@ +package com.amazonaws.serverless.proxy.spring.webfluxapp; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +@SpringBootApplication(exclude = { + org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class, + 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-struts2/pom.xml b/aws-serverless-java-container-struts2/pom.xml deleted file mode 100644 index 49942915c..000000000 --- a/aws-serverless-java-container-struts2/pom.xml +++ /dev/null @@ -1,219 +0,0 @@ - - - 4.0.0 - - aws-serverless-java-container-struts2 - AWS Serverless Java container support - Struts2 implementation - Allows Java applications written for the Struts2 framework to run in AWS Lambda - https://aws.amazon.com/lambda - 1.4-SNAPSHOT - - - com.amazonaws.serverless - aws-serverless-java-container - 1.4-SNAPSHOT - - - - 2.5.20 - 2.9.8 - - - - - - com.amazonaws.serverless - aws-serverless-java-container-core - 1.4-SNAPSHOT - - - - org.apache.struts - struts2-core - ${struts2.version} - - - commons-io - commons-io - - - - - - org.apache.struts - struts2-json-plugin - ${struts2.version} - test - - - - org.apache.struts - struts2-junit-plugin - ${struts2.version} - test - - - - - commons-codec - commons-codec - 1.10 - test - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - - org.apache.logging.log4j - log4j-to-slf4j - 2.11.1 - - - org.slf4j - slf4j-api - - - - - - javax.el - javax.el-api - 2.2.4 - test - - - - javax.servlet - jsp-api - 2.0 - test - - - - org.glassfish.web - javax.el - 2.2.4 - test - - - - - - - org.jacoco - jacoco-maven-plugin - 0.8.1 - - ${basedir}/target/coverage-reports/jacoco-unit.exec - ${basedir}/target/coverage-reports/jacoco-unit.exec - - - - default-prepare-agent - - prepare-agent - - - - jacoco-site - package - - report - - - - jacoco-check - test - - check - - - true - - BUNDLE - - - INSTRUCTION - COVEREDRATIO - ${jacoco.minCoverage} - - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.9 - - always - - - - com.github.spotbugs - spotbugs-maven-plugin - 3.1.1 - - - Max - - Low - - true - - ${project.build.directory}/spotbugs - - - - com.h3xstream.findsecbugs - findsecbugs-plugin - 1.7.1 - - - - - - - analyze-compile - compile - - check - - - - - - org.owasp - dependency-check-maven - ${dependencyCheck.version} - - true - - ${project.basedir}/../owasp-suppression.xml - - 7 - - - - - check - - - - - - - diff --git a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java deleted file mode 100644 index 003390ec0..000000000 --- a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.amazonaws.serverless.proxy.struts2; - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; -import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; -import com.amazonaws.serverless.proxy.ExceptionHandler; -import com.amazonaws.serverless.proxy.RequestReader; -import com.amazonaws.serverless.proxy.ResponseWriter; -import com.amazonaws.serverless.proxy.SecurityContextWriter; -import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; -import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; -import com.amazonaws.serverless.proxy.internal.testutils.Timer; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -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 java.util.EnumSet; -import java.util.concurrent.CountDownLatch; - -/** - * A Lambda handler to initialize the Struts2 filter and proxy the requests. - * - * @param request type - * @param response type - */ -public class Struts2LambdaContainerHandler extends AwsLambdaServletContainerHandler { - - private static final Logger log = LoggerFactory.getLogger(Struts2LambdaContainerHandler.class); - - public static final String HEADER_STRUTS_STATUS_CODE = "X-Struts-StatusCode"; - - private static final String TIMER_STRUTS_2_CONTAINER_CONSTRUCTOR = "STRUTS2_CONTAINER_CONSTRUCTOR"; - private static final String TIMER_STRUTS_2_HANDLE_REQUEST = "STRUTS2_HANDLE_REQUEST"; - private static final String TIMER_STRUTS_2_COLD_START_INIT = "STRUTS2_COLD_START_INIT"; - private static final String STRUTS_FILTER_NAME = "Struts2Filter"; - - private boolean initialized; - - public static Struts2LambdaContainerHandler getAwsProxyHandler() { - return new Struts2LambdaContainerHandler( - AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler()); - } - - public Struts2LambdaContainerHandler(Class requestTypeClass, - Class responseTypeClass, - RequestReader requestReader, - ResponseWriter responseWriter, - SecurityContextWriter securityContextWriter, - ExceptionHandler exceptionHandler) { - - super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); - Timer.start(TIMER_STRUTS_2_CONTAINER_CONSTRUCTOR); - this.initialized = false; - Timer.stop(TIMER_STRUTS_2_CONTAINER_CONSTRUCTOR); - } - - protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { - return new AwsHttpServletResponse(request, latch); - } - - @Override - protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, - AwsHttpServletResponse httpServletResponse, - Context lambdaContext) throws Exception { - Timer.start(TIMER_STRUTS_2_HANDLE_REQUEST); - if (!this.initialized) { - initialize(); - } - - 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_2_HANDLE_REQUEST); - } - - @Override - public void initialize() throws ContainerInitializationException { - log.info("Initialize Struts2 Lambda Application ..."); - Timer.start(TIMER_STRUTS_2_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), true, "/*"); - } catch (Exception e) { - throw new ContainerInitializationException("Could not initialize Struts2", e); - } - - this.initialized = true; - Timer.stop(TIMER_STRUTS_2_COLD_START_INIT); - log.info("... initialize of Struts2 Lambda Application completed!"); - } -} diff --git a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaHandler.java b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaHandler.java deleted file mode 100644 index 1e63daf99..000000000 --- a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.amazonaws.serverless.proxy.struts2; - -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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * The lambda handler to handle the requests. - *

- * - * com.amazonaws.serverless.proxy.struts2.Struts2LambdaHandler::handleRequest - * - */ -public class Struts2LambdaHandler implements RequestStreamHandler { - - private static final Logger log = LoggerFactory.getLogger(Struts2LambdaHandler.class); - - private final Struts2LambdaContainerHandler handler = Struts2LambdaContainerHandler - .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-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java deleted file mode 100644 index d362030f5..000000000 --- a/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java +++ /dev/null @@ -1,326 +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.struts2; - - -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.struts2.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.StrutsJUnit4TestCase; -import org.junit.Test; - -import javax.ws.rs.core.Response; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -/** - * Unit test class for the Struts2 AWS_PROXY default implementation - */ -public class Struts2AwsProxyTest extends StrutsJUnit4TestCase { - private static final String CUSTOM_HEADER_KEY = "x-custom-header"; - private static final String CUSTOM_HEADER_VALUE = "my-custom-value"; - private static final String AUTHORIZER_PRINCIPAL_ID = "test-principal-" + UUID.randomUUID().toString(); - 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 ObjectMapper objectMapper = new ObjectMapper(); - private final Struts2LambdaContainerHandler handler = Struts2LambdaContainerHandler - .getAwsProxyHandler(); - private static Context lambdaContext = new MockLambdaContext(); - - @Test - public void headers_getHeaders_echo() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") - .queryString("mode", "headers") - .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .json() - .build(); - - AwsProxyResponse output = handler.proxy(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); - - validateMapResponseModel(output); - } - - @Test - public void context_servletResponse_setCustomHeader() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET") - .queryString("customHeader", "true") - .json() - .build(); - - AwsProxyResponse output = handler.proxy(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertTrue(output.getMultiValueHeaders().containsKey("XX")); - } - - @Test - public void context_serverInfo_correctContext() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET") - .queryString(QUERY_STRING_KEY, "Hello Struts2") - .header("Content-Type", "application/json") - .queryString("contentType", "true") - .build(); - AwsProxyResponse output = handler.proxy(request, lambdaContext); - for (String header : output.getMultiValueHeaders().keySet()) { - System.out.println(header + ": " + output.getMultiValueHeaders().getFirst(header)); - } - assertEquals(200, output.getStatusCode()); - assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); - System.out.println("Body: " + output.getBody()); - - validateSingleValueModel(output, "Hello Struts2"); - } - - @Test - public void queryString_uriInfo_echo() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") - .queryString("mode", "query-string") - .queryString(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) - .json() - .build(); - - - AwsProxyResponse output = handler.proxy(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); - - validateMapResponseModel(output); - } - - @Test - public void requestScheme_valid_expectHttps() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") - .queryString("mode", "scheme") - .queryString(QUERY_STRING_KEY, QUERY_STRING_ENCODED_VALUE) - .json() - .build(); - - AwsProxyResponse output = handler.proxy(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() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") - .queryString("mode", "principal") - .json() - .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID) - .build(); - - AwsProxyResponse output = handler.proxy(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() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/unknown", "GET").build(); - - AwsProxyResponse output = handler.proxy(request, lambdaContext); - assertEquals(404, output.getStatusCode()); - } - - @Test - public void error_contentType_invalidContentType() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "POST") - .queryString("mode", "content-type") - .header("Content-Type", "application/octet-stream") - .body("asdasdasd") - .build(); - - AwsProxyResponse output = handler.proxy(request, lambdaContext); - assertEquals(415, output.getStatusCode()); - } - - @Test - public void error_statusCode_methodNotAllowed() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "POST") - .queryString("mode", "not-allowed") - .json() - .build(); - - AwsProxyResponse output = handler.proxy(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); - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "POST") - .json() - .body(objectMapper.writeValueAsString(value)) - .build(); - - AwsProxyResponse output = handler.proxy(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - assertNotNull(output.getBody()); - - validateSingleValueModel(output, "{\"message\":\"my-custom-value\"}"); - } - - @Test - public void statusCode_responseStatusCode_customStatusCode() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") - .queryString("mode", "custom-status-code") - .queryString("status", "201") - .json() - .build(); - - AwsProxyResponse output = handler.proxy(request, lambdaContext); - assertEquals(201, output.getStatusCode()); - } - - @Test - public void base64_binaryResponse_base64Encoding() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET").build(); - - AwsProxyResponse response = handler.proxy(request, lambdaContext); - assertNotNull(response.getBody()); - assertTrue(Base64.isBase64(response.getBody())); - } - - @Test - public void exception_mapException_mapToNotImplemented() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "POST") - .queryString("mode", "not-implemented") - .build(); - - AwsProxyResponse response = handler.proxy(request, lambdaContext); - assertNotNull(response.getBody()); - assertEquals("null", response.getBody()); - assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), response.getStatusCode()); - } - - @Test - public void stripBasePath_route_shouldRouteCorrectly() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/custompath/echo", "GET") - .json() - .queryString(QUERY_STRING_KEY, "stripped") - .build(); - handler.stripBasePath("/custompath"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); - assertEquals(200, output.getStatusCode()); - validateSingleValueModel(output, "stripped"); - handler.stripBasePath(""); - } - - @Test - public void stripBasePath_route_shouldReturn404() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/custompath/echo/status-code", "GET") - .json() - .queryString("status", "201") - .build(); - handler.stripBasePath("/custom"); - AwsProxyResponse output = handler.proxy(request, lambdaContext); - assertEquals(404, output.getStatusCode()); - handler.stripBasePath(""); - } - - @Test - public void securityContext_injectPrincipal_expectPrincipalName() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") - .queryString("mode", "principal") - .authorizerPrincipal(USER_PRINCIPAL).build(); - - AwsProxyResponse resp = handler.proxy(request, lambdaContext); - assertEquals(200, resp.getStatusCode()); - validateSingleValueModel(resp, USER_PRINCIPAL); - } - - @Test - public void queryParam_encoding_expectUnencodedParam() { - String paramValue = "p%2Fz%2B3"; - String decodedParam = ""; - try { - decodedParam = URLDecoder.decode(paramValue, "UTF-8"); - System.out.println(decodedParam); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - fail("Could not decode parameter"); - } - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET").queryString(QUERY_STRING_KEY, decodedParam) - .build(); - - AwsProxyResponse resp = handler.proxy(request, lambdaContext); - assertEquals(200, resp.getStatusCode()); - validateSingleValueModel(resp, decodedParam); - } - - @Test - public void queryParam_encoding_expectEncodedParam() { - String paramValue = "p%2Fz%2B3"; - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET").queryString(QUERY_STRING_KEY, paramValue) - .build(); - - AwsProxyResponse resp = handler.proxy(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 = objectMapper.readValue(output.getBody(), typeRef); - assertNotNull(response.get(key)); - assertEquals(value, response.get(key)); - } catch (IOException e) { - fail("Exception while parsing response body: " + e.getMessage()); - e.printStackTrace(); - } - } - - private void validateSingleValueModel(AwsProxyResponse output, String value) { - try { - assertNotNull(output.getBody()); - assertEquals(value, objectMapper.readerFor(String.class).readValue(output.getBody())); - } catch (Exception e) { - fail("Exception while parsing response body: " + e.getMessage()); - e.printStackTrace(); - } - } -} diff --git a/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoAction.java b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoAction.java deleted file mode 100644 index bfaa0a69f..000000000 --- a/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoAction.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.amazonaws.serverless.proxy.struts2.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-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoRequestInfoAction.java b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoRequestInfoAction.java deleted file mode 100644 index 1d9c1697e..000000000 --- a/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoRequestInfoAction.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.amazonaws.serverless.proxy.struts2.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-struts2/src/test/resources/struts.xml b/aws-serverless-java-container-struts2/src/test/resources/struts.xml deleted file mode 100644 index 0c5dd8328..000000000 --- a/aws-serverless-java-container-struts2/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 d89ca75b6..4afdc9996 100644 --- a/aws-serverless-jersey-archetype/pom.xml +++ b/aws-serverless-jersey-archetype/pom.xml @@ -4,17 +4,18 @@ com.amazonaws.serverless aws-serverless-java-container - 1.4-SNAPSHOT + 2.1.5-SNAPSHOT com.amazonaws.serverless.archetypes aws-serverless-jersey-archetype - 1.4-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 + @@ -47,7 +48,7 @@ org.apache.maven.archetype archetype-packaging - 3.0.1 + 3.4.0 @@ -56,7 +57,7 @@ org.apache.maven.plugins maven-resources-plugin - 3.1.0 + 3.3.1 \ @@ -64,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/META-INF/maven/archetype-metadata.xml b/aws-serverless-jersey-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml index b934c4a4a..dc8cb6204 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ b/aws-serverless-jersey-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -24,7 +24,7 @@ - sam.yaml + template.yml README.md build.gradle diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/README.md index de5f4671a..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,44 +16,47 @@ #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. -The project folder also includes a `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). +The project folder also includes a `template.yml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with the [SAM CLI](https://github.com/awslabs/aws-sam-cli). -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. +#[[##]]# Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) + +#[[##]]# Building the project +You can use the SAM CLI to quickly build the project ```bash $ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-jersey-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false $ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` +$ sam build +Building resource '\${resourceName}Function' +Running JavaGradleWorkflow:GradleBuild +Running JavaGradleWorkflow:CopyArtifacts -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. +Build Succeeded -First, install SAM local: +Built Artifacts : .aws-sam/build +Built Template : .aws-sam/build/template.yaml -```bash -$ npm install -g aws-sam-local +Commands you can use next +========================= +[*] Invoke Function: sam local invoke +[*] Deploy: sam deploy --guided ``` -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. +#[[##]]# Testing locally with the SAM CLI + +From the project root folder - where the `template.yml` file is located - start the API with the SAM CLI. ```bash -$ sam local start-api --template sam.yaml +$ sam local start-api ... -Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] +Mounting ${groupId}.StreamLambdaHandler::handleRequest (java11) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] ... ``` @@ -67,54 +70,22 @@ $ curl -s http://127.0.0.1:3000/ping | python -m json.tool } ``` -## Deploying to AWS -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` +#[[##]]# Deploying to AWS +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - ``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessJerseyApi --capabilities CAPABILITY_IAM +$ sam deploy --guided ``` -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessJerseyApi` key of the `Outputs` property: +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` -$ aws cloudformation describe-stacks --stack-name ServerlessJerseyApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessJerseyApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Jersey API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessJerseyApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - +... +------------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +------------------------------------------------------------------------------------------------------------- +\${resourceName}Api - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets +------------------------------------------------------------------------------------------------------------- ``` Copy the `OutputValue` into a browser or use curl to test your first request: diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/build.gradle index eda69dc96..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 @@ -6,24 +6,26 @@ repositories { } dependencies { - compile ( - 'com.amazonaws:aws-lambda-java-core:1.2.0', - 'com.amazonaws.serverless:aws-serverless-java-container-jersey:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', - 'io.symphonia:lambda-logging:1.0.1' + implementation ( + 'com.amazonaws.serverless:aws-serverless-java-container-jersey:[2.0-SNAPSHOT,)', + 'com.fasterxml.jackson.core:jackson-databind:2.19.1', ) - compile("org.glassfish.jersey.media:jersey-media-json-jackson:2.27") { + 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" } - compile("org.glassfish.jersey.inject:jersey-hk2:2.27") { + implementation("org.glassfish.jersey.inject:jersey-hk2:3.1.10") { exclude group: 'javax.inject', module: "javax.inject" } - testCompile("junit:junit:4.12") + 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 b94697c7f..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.27 - 2.9.8 + + 3.1.10 + 2.18.3 + 5.12.1 @@ -24,11 +26,25 @@ 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 jersey-media-json-jackson - ${jersey.version} + \${jersey.version} com.fasterxml.jackson.core @@ -48,13 +64,13 @@ com.fasterxml.jackson.core jackson-databind - ${jackson.version} + \${jackson.version} org.glassfish.jersey.inject jersey-hk2 - ${jersey.version} + \${jersey.version} @@ -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 - 2.3 + 3.6.0 false @@ -107,7 +134,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.1.1 + 3.4.2 default-jar @@ -115,11 +142,19 @@ + + org.apache.maven.plugins + maven-install-plugin + 3.1.2 + + true + + org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.8.1 copy-dependencies @@ -137,7 +172,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.1.0 + 3.7.1 zip-assembly diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java index a79670b7f..768a5ad98 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java @@ -8,6 +8,7 @@ import org.glassfish.jersey.jackson.JacksonFeature; import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.ServerProperties; import java.io.IOException; import java.io.InputStream; @@ -18,8 +19,15 @@ public class StreamLambdaHandler implements RequestStreamHandler { private static final ResourceConfig jerseyApplication = new ResourceConfig() - .register(PingResource.class) - .register(JacksonFeature.class); + // properties to speed up Jersey start time + .property(ServerProperties.FEATURE_AUTO_DISCOVERY_DISABLE,true) + .property(ServerProperties.WADL_FEATURE_DISABLE,true) + .property(ServerProperties.METAINF_SERVICES_LOOKUP_DISABLE,true) + .property(ServerProperties.BV_FEATURE_DISABLE,true) + .property(ServerProperties.JSON_PROCESSING_FEATURE_DISABLE,true) + .property(ServerProperties.MOXY_JSON_FEATURE_DISABLE,true) + .register(PingResource.class) + .register(JacksonFeature.class); private static final JerseyLambdaContainerHandler handler = JerseyLambdaContainerHandler.getAwsProxyHandler(jerseyApplication); 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/sam.yaml b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/template.yml similarity index 93% rename from aws-serverless-jersey-archetype/src/main/resources/archetype-resources/sam.yaml rename to aws-serverless-jersey-archetype/src/main/resources/archetype-resources/template.yml index 9df80ae88..0ee7360dd 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/sam.yaml +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/template.yml @@ -32,13 +32,13 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: ${groupId}.StreamLambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/${artifactId}-${version}-lambda-package.zip + Runtime: java21 + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 15 Events: - GetResource: + ProxyResource: Type: Api Properties: Path: /{proxy+} diff --git a/aws-serverless-spark-archetype/pom.xml b/aws-serverless-spark-archetype/pom.xml deleted file mode 100644 index 65c870e59..000000000 --- a/aws-serverless-spark-archetype/pom.xml +++ /dev/null @@ -1,80 +0,0 @@ - - 4.0.0 - - - com.amazonaws.serverless - aws-serverless-java-container - 1.4-SNAPSHOT - - - com.amazonaws.serverless.archetypes - aws-serverless-spark-archetype - 1.4-SNAPSHOT - maven-archetype - - - 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 - - - - - - - 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-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 45dc2c1a7..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 - - * - - - - - - sam.yaml - README.md - build.gradle - - - - \ No newline at end of file diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/README.md deleted file mode 100644 index 82a59465b..000000000 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/README.md +++ /dev/null @@ -1,128 +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 `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). - -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. -```bash -$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-spark-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false -$ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` - -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. - -First, install SAM local: - -```bash -$ npm install -g aws-sam-local -``` - -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. - -```bash -$ sam local start-api --template sam.yaml - -... -Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) 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 -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` - -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSparkApi --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessSparkApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSparkApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessSparkApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Spark API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessSparkApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - -``` - -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-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 ace7a6b3e..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 { - compile ( - 'com.sparkjava:spark-core:2.8.0', - 'com.amazonaws.serverless:aws-serverless-java-container-spark:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', - 'io.symphonia:lambda-logging:1.0.1' - ) - - testCompile("junit:junit:4.12") -} - -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/pom.xml b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml deleted file mode 100644 index eec41cd2a..000000000 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml +++ /dev/null @@ -1,152 +0,0 @@ -#set($dollar = '$') - - - 4.0.0 - - \${groupId} - \${artifactId} - \${version} - jar - - Serverless Spark API - https://github.com/awslabs/aws-serverless-java-container - - - 1.8 - 1.8 - 2.9.8 - 2.8.0 - - - - - com.amazonaws.serverless - aws-serverless-java-container-spark - ${project.version} - - - - com.sparkjava - spark-core - ${spark.version} - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - - junit - junit - 4.12 - test - - - - - - shaded-jar - - - - org.apache.maven.plugins - maven-shade-plugin - 2.3 - - false - - - - package - - shade - - - - - - 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 - - - - - - - - - - - assembly-zip - - true - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.1.1 - - - default-jar - none - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.1.1 - - - copy-dependencies - package - - copy-dependencies - - - ${dollar}{project.build.directory}${dollar}{file.separator}lib - runtime - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.1.0 - - - zip-assembly - package - - single - - - ${dollar}{project.artifactId}-${dollar}{project.version} - - src${dollar}{file.separator}assembly${dollar}{file.separator}bin.xml - - false - - - - - - - - - diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/sam.yaml b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/sam.yaml deleted file mode 100644 index 674c769af..000000000 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/sam.yaml +++ /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: java8 - CodeUri: target/${artifactId}-${version}-lambda-package.zip - MemorySize: 512 - Policies: AWSLambdaBasicExecutionRole - Timeout: 15 - Events: - GetResource: - 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/main/resources/archetype-resources/src/assembly/bin.xml b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml deleted file mode 100644 index fcb935036..000000000 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml +++ /dev/null @@ -1,32 +0,0 @@ - - lambda-package - - zip - - false - - - - ${project.build.directory}${file.separator}lib - - websocket* - jetty-http* - jetty-client* - jetty-webapp* - jetty-xml* - jetty-io* - - lib - - - - ${project.build.directory}${file.separator}classes - - ** - - ${file.separator} - - - \ No newline at end of file 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/src/test/java/StreamLambdaHandlerTest.java b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java deleted file mode 100644 index 3f232ebc6..000000000 --- a/aws-serverless-spark-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-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 35331470f..2a2f59b8b 100644 --- a/aws-serverless-spring-archetype/pom.xml +++ b/aws-serverless-spring-archetype/pom.xml @@ -4,17 +4,18 @@ com.amazonaws.serverless aws-serverless-java-container - 1.4-SNAPSHOT + 2.1.5-SNAPSHOT com.amazonaws.serverless.archetypes aws-serverless-spring-archetype - 1.4-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 + @@ -47,7 +48,7 @@ org.apache.maven.archetype archetype-packaging - 3.0.1 + 3.4.0 @@ -56,7 +57,7 @@ org.apache.maven.plugins maven-resources-plugin - 3.1.0 + 3.3.1 \ @@ -65,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/META-INF/maven/archetype-metadata.xml b/aws-serverless-spring-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml index 9295c6457..e279efa50 100644 --- a/aws-serverless-spring-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ b/aws-serverless-spring-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -30,7 +30,7 @@ - sam.yaml + template.yml README.md build.gradle diff --git a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/README.md index 1bb929910..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,44 +16,47 @@ #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. -The project folder also includes a `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). +The project folder also includes a `template.yml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with the [SAM CLI](https://github.com/awslabs/aws-sam-cli). -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. +#[[##]]# Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) + +#[[##]]# Building the project +You can use the SAM CLI to quickly build the project ```bash -$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-spring-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false +$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-jersey-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false $ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` +$ sam build +Building resource '\${resourceName}Function' +Running JavaGradleWorkflow:GradleBuild +Running JavaGradleWorkflow:CopyArtifacts -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. +Build Succeeded -First, install SAM local: +Built Artifacts : .aws-sam/build +Built Template : .aws-sam/build/template.yaml -```bash -$ npm install -g aws-sam-local +Commands you can use next +========================= +[*] Invoke Function: sam local invoke +[*] Deploy: sam deploy --guided ``` -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. +#[[##]]# Testing locally with the SAM CLI + +From the project root folder - where the `template.yml` file is located - start the API with the SAM CLI. ```bash -$ sam local start-api --template sam.yaml +$ sam local start-api ... -Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] +Mounting ${groupId}.StreamLambdaHandler::handleRequest (java11) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] ... ``` @@ -67,54 +70,22 @@ $ curl -s http://127.0.0.1:3000/ping | python -m json.tool } ``` -## Deploying to AWS -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` +#[[##]]# Deploying to AWS +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - ``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringApi --capabilities CAPABILITY_IAM +$ sam deploy --guided ``` -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessSpringApi` key of the `Outputs` property: +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessSpringApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Spring API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessSpringApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - +... +------------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +------------------------------------------------------------------------------------------------------------- +\${resourceName}Api - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets +------------------------------------------------------------------------------------------------------------- ``` Copy the `OutputValue` into a browser or use curl to test your first request: diff --git a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/build.gradle index ec235eba6..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 @@ -6,18 +6,22 @@ repositories { } dependencies { - compile ( - 'org.springframework:spring-webmvc:5.1.1.RELEASE', - 'org.springframework:spring-context:5.1.1.RELEASE', - 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', - 'org.apache.logging.log4j:log4j-core:2.8.2', - 'org.apache.logging.log4j:log4j-api:2.8.2', - 'org.apache.logging.log4j:log4j-slf4j-impl:2.8.2', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', - 'com.amazonaws:aws-lambda-java-log4j2:1.1.0', + implementation ( + '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', ) - testCompile("junit:junit:4.12") + 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 f9c2ee985..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.1.1.RELEASE - 4.12 - 2.8.2 + 6.2.6 + 5.12.1 + 2.24.2 @@ -27,17 +27,31 @@ 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 spring-webmvc - ${spring.version} + \${spring.version} org.springframework spring-context - ${spring.version} + \${spring.version} org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.8.1 copy-dependencies @@ -163,7 +195,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.1.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/sam.yaml b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/template.yml similarity index 93% rename from aws-serverless-spring-archetype/src/main/resources/archetype-resources/sam.yaml rename to aws-serverless-spring-archetype/src/main/resources/archetype-resources/template.yml index 374dfe593..fe49737e5 100644 --- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/sam.yaml +++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/template.yml @@ -32,13 +32,13 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: ${groupId}.StreamLambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/${artifactId}-${version}-lambda-package.zip + Runtime: java21 + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 15 Events: - GetResource: + ProxyResource: Type: Api Properties: Path: /{proxy+} diff --git a/aws-serverless-springboot-archetype/pom.xml b/aws-serverless-springboot-archetype/pom.xml deleted file mode 100644 index 011cc1a95..000000000 --- a/aws-serverless-springboot-archetype/pom.xml +++ /dev/null @@ -1,79 +0,0 @@ - - 4.0.0 - - - com.amazonaws.serverless - aws-serverless-java-container - 1.4-SNAPSHOT - - - com.amazonaws.serverless.archetypes - aws-serverless-springboot-archetype - 1.4-SNAPSHOT - maven-archetype - - - 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 - - - - - - - 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-springboot-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/README.md deleted file mode 100644 index 43a3683f5..000000000 --- a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/README.md +++ /dev/null @@ -1,128 +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 `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). - -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. -```bash -$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-springboot-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false -$ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` - -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. - -First, install SAM local: - -```bash -$ npm install -g aws-sam-local -``` - -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. - -```bash -$ sam local start-api --template sam.yaml - -... -Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) 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 -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` - -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringApi --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessSpringApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessSpringApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Spring API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessSpringApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - -``` - -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-springboot-archetype/src/main/resources/archetype-resources/sam.yaml b/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/sam.yaml deleted file mode 100644 index 3fbd348b2..000000000 --- a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/sam.yaml +++ /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 Spring Boot API - ${groupId}::${artifactId} -Globals: - Api: - EndpointConfiguration: REGIONAL - -Resources: - ${resourceName}Function: - Type: AWS::Serverless::Function - Properties: - Handler: ${groupId}.StreamLambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/${artifactId}-${version}-lambda-package.zip - MemorySize: 512 - Policies: AWSLambdaBasicExecutionRole - Timeout: 30 - Events: - GetResource: - 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-springboot-archetype/src/main/resources/archetype-resources/src/main/java/Application.java b/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/src/main/java/Application.java deleted file mode 100644 index 0d25d61bd..000000000 --- a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/src/main/java/Application.java +++ /dev/null @@ -1,72 +0,0 @@ -#macro(loggingOff) - logging.level.root:OFF -#end -#set($logging = "#loggingOff()") -#set($logging = $logging.replaceAll("\n", "").trim()) -package ${groupId}; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.support.SpringBootServletInitializer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.web.servlet.HandlerAdapter; -import org.springframework.web.servlet.HandlerExceptionResolver; -import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import ${groupId}.controller.PingController; - - -@SpringBootApplication -// We use direct @Import instead of @ComponentScan to speed up cold starts -// @ComponentScan(basePackages = "${groupId}.controller") -@Import({ PingController.class }) -public class Application extends SpringBootServletInitializer { - - /* - * Create required HandlerMapping, to avoid several default HandlerMapping instances being created - */ - @Bean - public HandlerMapping handlerMapping() { - return new RequestMappingHandlerMapping(); - } - - /* - * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created - */ - @Bean - public HandlerAdapter handlerAdapter() { - return new RequestMappingHandlerAdapter(); - } - - /* - * optimization - avoids creating default exception resolvers; not required as the serverless container handles - * all exceptions - * - * By default, an ExceptionHandlerExceptionResolver is created which creates many dependent object, including - * an expensive ObjectMapper instance. - * - * To enable custom @ControllerAdvice classes remove this bean. - */ - @Bean - public HandlerExceptionResolver handlerExceptionResolver() { - return new HandlerExceptionResolver() { - - @Override - public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { - return null; - } - }; - } - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } -} \ No newline at end of file diff --git a/aws-serverless-springboot-archetype/src/test/resources/projects/base/goal.txt b/aws-serverless-springboot-archetype/src/test/resources/projects/base/goal.txt deleted file mode 100644 index 597acc768..000000000 --- a/aws-serverless-springboot-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-springboot2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/aws-serverless-springboot2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml deleted file mode 100644 index ec02bb6ab..000000000 --- a/aws-serverless-springboot2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - src/main/java - - **/*.java - - - - src/main/resources - - **/*.properties - - - - src/test/java - - **/*.java - - - - src/assembly - - * - - - - - - sam.yaml - README.md - build.gradle - - - - \ No newline at end of file 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 3f99b3445..000000000 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/README.md +++ /dev/null @@ -1,128 +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 `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). - -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. -```bash -$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-springboot2-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false -$ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` - -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. - -First, install SAM local: - -```bash -$ npm install -g aws-sam-local -``` - -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. - -```bash -$ sam local start-api --template sam.yaml - -... -Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) 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 -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` - -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringApi --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessSpringApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessSpringApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Spring API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessSpringApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - -``` - -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/build.gradle b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle deleted file mode 100644 index 7bd0e7fec..000000000 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -plugins { - id 'org.springframework.boot' version '2.1.1.RELEASE' -} -apply plugin: 'java' - -repositories { - jcenter() - mavenLocal() - mavenCentral() -} - -dependencies { - compile ( - 'org.springframework.boot:spring-boot-starter-web:2.1.1.RELEASE', - 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', - 'io.symphonia:lambda-logging:1.0.1' - ) - - testCompile("junit:junit:4.12") -} - -task buildZip(type: Zip) { - from compileJava - from processResources - into('lib') { - from(configurations.compileClasspath) { - exclude 'tomcat-embed-*' - } - } -} - -build.dependsOn buildZip diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/pom.xml deleted file mode 100644 index fcdf963fd..000000000 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/pom.xml +++ /dev/null @@ -1,141 +0,0 @@ -#set($dollar = '$') - - - 4.0.0 - - \${groupId} - \${artifactId} - \${version} - jar - - Serverless Spring Boot 2 API - https://github.com/awslabs/aws-serverless-java-container - - - org.springframework.boot - spring-boot-starter-parent - 2.1.1.RELEASE - - - - 1.8 - 1.8 - - - - - com.amazonaws.serverless - aws-serverless-java-container-spring - ${project.version} - - - - org.springframework.boot - spring-boot-starter-web - - - - junit - junit - 4.12 - test - - - - - - shaded-jar - - - - org.apache.maven.plugins - maven-shade-plugin - 2.3 - - false - - - - package - - shade - - - - - org.apache.tomcat.embed:* - - - - - - - - - - - assembly-zip - - true - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.1.1 - - - default-jar - none - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.1.1 - - - copy-dependencies - package - - copy-dependencies - - - ${dollar}{project.build.directory}${dollar}{file.separator}lib - runtime - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.1.0 - - - zip-assembly - package - - single - - - ${dollar}{project.artifactId}-${dollar}{project.version} - - src${dollar}{file.separator}assembly${dollar}{file.separator}bin.xml - - false - - - - - - - - - diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/controller/PingController.java b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/controller/PingController.java deleted file mode 100644 index 94f517f07..000000000 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/controller/PingController.java +++ /dev/null @@ -1,20 +0,0 @@ -package ${groupId}.controller; - - -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import java.util.HashMap; -import java.util.Map; - - -@RestController -@EnableWebMvc -public class PingController { - @RequestMapping(path = "/ping", method = RequestMethod.GET) - public Map ping() { - Map pong = new HashMap<>(); - pong.put("pong", "Hello, World!"); - return pong; - } -} diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties deleted file mode 100644 index ec1cb9792..000000000 --- a/aws-serverless-springboot2-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-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/archetype.properties b/aws-serverless-springboot2-archetype/src/test/resources/projects/base/archetype.properties deleted file mode 100644 index 7df3bf6e1..000000000 --- a/aws-serverless-springboot2-archetype/src/test/resources/projects/base/archetype.properties +++ /dev/null @@ -1,3 +0,0 @@ -groupId=test.service -artifactId=springboot-archetype-test -version=1.0-SNAPSHOT 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-springboot2-archetype/pom.xml b/aws-serverless-springboot3-archetype/pom.xml similarity index 87% rename from aws-serverless-springboot2-archetype/pom.xml rename to aws-serverless-springboot3-archetype/pom.xml index 2ade62df5..74fac6156 100644 --- a/aws-serverless-springboot2-archetype/pom.xml +++ b/aws-serverless-springboot3-archetype/pom.xml @@ -4,17 +4,18 @@ com.amazonaws.serverless aws-serverless-java-container - 1.4-SNAPSHOT + 2.1.5-SNAPSHOT com.amazonaws.serverless.archetypes - aws-serverless-springboot2-archetype - 1.4-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 + @@ -47,7 +48,7 @@ org.apache.maven.archetype archetype-packaging - 3.0.1 + 3.4.0 @@ -56,7 +57,7 @@ org.apache.maven.plugins maven-resources-plugin - 3.1.0 + 3.3.1 \ @@ -64,7 +65,7 @@ org.apache.maven.plugins maven-archetype-plugin - 3.0.1 + 3.4.0 diff --git a/aws-serverless-springboot-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 96% rename from aws-serverless-springboot-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 index ec02bb6ab..5379692ba 100644 --- a/aws-serverless-springboot-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml +++ b/aws-serverless-springboot3-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -30,7 +30,7 @@ - sam.yaml + template.yml README.md build.gradle diff --git a/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/README.md new file mode 100644 index 000000000..311c40aee --- /dev/null +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/README.md @@ -0,0 +1,99 @@ +#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/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-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-springboot-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/pom.xml similarity index 71% rename from aws-serverless-springboot-archetype/src/main/resources/archetype-resources/pom.xml rename to aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/pom.xml index e0ee7b747..73d407dd8 100644 --- a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/pom.xml @@ -10,40 +10,71 @@ \${version} jar - Serverless Spring Boot 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 - 1.5.17.RELEASE + 3.4.5 - 1.8 - 1.8 + 17 + 5.12.1 com.amazonaws.serverless - aws-serverless-java-container-spring + 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 spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + - junit - junit - 4.12 + org.junit.jupiter + junit-jupiter test + + + + org.junit + junit-bom + ${junit.version} + import + pom + + + + shaded-jar @@ -52,7 +83,7 @@ org.apache.maven.plugins maven-shade-plugin - 2.3 + 3.6.0 false @@ -86,7 +117,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.1.1 + 3.4.2 default-jar @@ -94,11 +125,19 @@ + + org.apache.maven.plugins + maven-install-plugin + 3.1.2 + + true + + org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.8.1 copy-dependencies @@ -116,7 +155,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.1.0 + 3.7.1 zip-assembly diff --git a/aws-serverless-springboot-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-springboot-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-springboot3-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 new file mode 100644 index 000000000..1b74086f7 --- /dev/null +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/src/main/java/Application.java @@ -0,0 +1,24 @@ +#macro(loggingOff) + logging.level.root:OFF +#end +#set($logging = "#loggingOff()") +#set($logging = $logging.replaceAll("\n", "").trim()) +package ${groupId}; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +import ${groupId}.controller.PingController; + + +@SpringBootApplication +// We use direct @Import instead of @ComponentScan to speed up cold starts +// @ComponentScan(basePackages = "${groupId}.controller") +@Import({ PingController.class }) +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} \ No newline at end of file diff --git a/aws-serverless-springboot-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 100% rename from aws-serverless-springboot-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 diff --git a/aws-serverless-springboot-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-springboot-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-springboot-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-springboot-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-springboot-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-springboot-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-springboot-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-springboot-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/sam.yaml b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/template.yml similarity index 93% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/sam.yaml rename to aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/template.yml index 970025c41..fe96b1fa2 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/sam.yaml +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/template.yml @@ -32,13 +32,13 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: ${groupId}.StreamLambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/${artifactId}-${version}-lambda-package.zip + Runtime: java21 + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 30 Events: - GetResource: + ProxyResource: Type: Api Properties: Path: /{proxy+} diff --git a/aws-serverless-springboot-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-springboot-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-struts2-archetype/pom.xml b/aws-serverless-struts2-archetype/pom.xml deleted file mode 100644 index c35780681..000000000 --- a/aws-serverless-struts2-archetype/pom.xml +++ /dev/null @@ -1,79 +0,0 @@ - - 4.0.0 - - - com.amazonaws.serverless - aws-serverless-java-container - 1.4-SNAPSHOT - - - com.amazonaws.serverless.archetypes - aws-serverless-struts2-archetype - 1.4-SNAPSHOT - maven-archetype - - - 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 - - - - - - - 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-struts2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/aws-serverless-struts2-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml deleted file mode 100644 index 91a53e562..000000000 --- a/aws-serverless-struts2-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 - - - - - - sam.yaml - README.md - build.gradle - - - - \ No newline at end of file diff --git a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/README.md b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/README.md deleted file mode 100644 index 7be306683..000000000 --- a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/README.md +++ /dev/null @@ -1,128 +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 `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). - -## Building the project -Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible zip file simply by running the maven package command from the project folder. -```bash -$ mvn archetype:generate -DartifactId=\${artifactId} -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-struts2-archetype -DarchetypeVersion=${project.version} -DgroupId=\${groupId} -Dversion=\${version} -Dinteractive=false -$ cd \${artifactId} -$ mvn clean package - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.546 s -[INFO] Finished at: 2018-02-15T08:39:33-08:00 -[INFO] Final Memory: XXM/XXXM -[INFO] ------------------------------------------------------------------------ -``` - -## Testing locally with SAM local -You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. - -First, install SAM local: - -```bash -$ npm install -g aws-sam-local -``` - -Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. - -```bash -$ sam local start-api --template sam.yaml - -... -Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) 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 -You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the project's root folder - where the `sam.yaml` file is located: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` - -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringApi --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `ServerlessSpringApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringApi -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessSpringApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "AWS Serverless Spring API - ${groupId}::${artifactId}", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "ExportName": "\${resourceName}Api", - "OutputKey": "\${resourceName}Api", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "ServerlessSpringApi", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - -``` - -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-struts2-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/build.gradle deleted file mode 100644 index 41046f45e..000000000 --- a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/build.gradle +++ /dev/null @@ -1,35 +0,0 @@ -apply plugin: 'java' - -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - compile ( - 'com.amazonaws.serverless:aws-serverless-java-container-struts2:[1.0,)', - 'org.apache.struts:struts2-convention-plugin:2.5.17', - 'org.apache.struts:struts2-rest-plugin:2.5.17', - 'org.apache.struts:struts2-bean-validation-plugin:2.5.17', - 'org.apache.struts:struts2-junit-plugin:2.5.17', - 'com.jgeppert.struts2:struts2-aws-lambda-support-plugin:1.0.0', - 'org.hibernate:hibernate-validator:4.3.2.Final', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', - 'org.apache.logging.log4j:log4j-core:2.8.2', - 'org.apache.logging.log4j:log4j-api:2.8.2', - 'org.apache.logging.log4j:log4j-slf4j-impl:2.8.2', - 'com.amazonaws:aws-lambda-java-log4j2:1.1.0', - ) - - testCompile("junit:junit:4.12") -} - -task buildZip(type: Zip) { - from compileJava - from processResources - into('lib') { - from(configurations.compileClasspath) - } -} - -build.dependsOn buildZip diff --git a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/pom.xml deleted file mode 100644 index a1ca2962f..000000000 --- a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/pom.xml +++ /dev/null @@ -1,147 +0,0 @@ - - - 4.0.0 - - \${groupId} - \${artifactId} - \${version} - jar - - Serverless Struts2 API - https://github.com/awslabs/aws-serverless-java-container - - - 1.8 - 1.8 - 2.5.20 - 2.9.8 - 4.12 - 2.11.1 - - - - - com.amazonaws.serverless - aws-serverless-java-container-struts2 - ${project.version} - - - - com.amazonaws - aws-lambda-java-core - 1.2.0 - - - - org.apache.struts - struts2-convention-plugin - ${struts2.version} - - - - org.apache.struts - struts2-rest-plugin - ${struts2.version} - - - - org.apache.struts - struts2-bean-validation-plugin - ${struts2.version} - - - - org.apache.struts - struts2-junit-plugin - ${struts2.version} - test - - - - - com.jgeppert.struts2 - struts2-aws-lambda-support-plugin - 1.1.0 - - - - - org.hibernate - hibernate-validator - 4.3.2.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} - - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - - - - org.apache.logging.log4j - log4j-api - ${log4j.version} - - - - org.apache.logging.log4j - log4j-slf4j-impl - ${log4j.version} - - - - com.amazonaws - aws-lambda-java-log4j2 - 1.1.0 - - - - junit - junit - ${junit.version} - test - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.1.0 - - - src/main/assembly/dist.xml - - - - - lambda - package - - single - - - - - - - diff --git a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/sam.yaml b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/sam.yaml deleted file mode 100644 index 023266fc7..000000000 --- a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/sam.yaml +++ /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 Struts2 API - ${groupId}::${artifactId} -Globals: - Api: - EndpointConfiguration: REGIONAL - -Resources: - ${resourceName}Function: - Type: AWS::Serverless::Function - Properties: - Handler: com.amazonaws.serverless.proxy.struts2.Struts2LambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/${artifactId}-${version}-lambda.zip - MemorySize: 512 - Policies: AWSLambdaBasicExecutionRole - Timeout: 30 - Events: - GetResource: - 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-struts2-archetype/src/main/resources/archetype-resources/src/main/assembly/dist.xml b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/src/main/assembly/dist.xml deleted file mode 100644 index 0466b85c5..000000000 --- a/aws-serverless-struts2-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-struts2-archetype/src/main/resources/archetype-resources/src/main/java/actions/PingController.java b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/src/main/java/actions/PingController.java deleted file mode 100644 index f3763f4de..000000000 --- a/aws-serverless-struts2-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-struts2-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties deleted file mode 100644 index ec1cb9792..000000000 --- a/aws-serverless-struts2-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-struts2-archetype/src/main/resources/archetype-resources/src/main/resources/log4j2.xml b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/src/main/resources/log4j2.xml deleted file mode 100644 index 55ed0d21c..000000000 --- a/aws-serverless-struts2-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-struts2-archetype/src/main/resources/archetype-resources/src/main/resources/struts.xml b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/src/main/resources/struts.xml deleted file mode 100644 index 5b86eb974..000000000 --- a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/src/main/resources/struts.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/src/test/java/StreamLambdaHandlerTest.java deleted file mode 100644 index f429ce66b..000000000 --- a/aws-serverless-struts2-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.struts2.Struts2LambdaHandler; - -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 Struts2LambdaHandler handler; - private static Context lambdaContext; - - @BeforeClass - public static void setUp() { - handler = new Struts2LambdaHandler(); - 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-struts2-archetype/src/test/resources/projects/base/archetype.properties b/aws-serverless-struts2-archetype/src/test/resources/projects/base/archetype.properties deleted file mode 100644 index d731ebe81..000000000 --- a/aws-serverless-struts2-archetype/src/test/resources/projects/base/archetype.properties +++ /dev/null @@ -1,3 +0,0 @@ -groupId=test.service -artifactId=struts2-archetype-test -version=1.0-SNAPSHOT diff --git a/aws-serverless-struts2-archetype/src/test/resources/projects/base/goal.txt b/aws-serverless-struts2-archetype/src/test/resources/projects/base/goal.txt deleted file mode 100644 index 597acc768..000000000 --- a/aws-serverless-struts2-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 new file mode 100755 index 000000000..247e09105 --- /dev/null +++ b/gha_build.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +WORKING_DIR=$(pwd) +FRAMEWORK=$1 +RUN_ARCHETYPE=$2 +RUN_SAMPLES=$3 +EXTRA_PARAMS=${*:4} + +echo "Starting build script for ${FRAMEWORK} with params ${EXTRA_PARAMS}" + +if [[ -z ${FRAMEWORK} ]] ; then + echo "Missing framework parameter" + exit 1 +fi + +function install { + # we skip tests for core because we assume they will be run in a separate branch of the workflow + cd ${WORKING_DIR}/aws-serverless-java-container-core && mvn -q clean install -DskipTests + if [[ "$?" -ne 0 ]]; then + exit 1 + fi + cd ${WORKING_DIR}/aws-serverless-java-container-$1 && mvn -q clean install ${@:2} + if [[ "$?" -ne 0 ]]; then + exit 1 + fi +} + +function archetype { + ARCHETYPE_NAME=aws-serverless-$1-archetype + PROJ_NAME=$1-archetype-test + 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=2.0-SNAPSHOT \ + -DarchetypeGroupId=com.amazonaws.serverless.archetypes \ + -DarchetypeArtifactId=${ARCHETYPE_NAME} \ + -DarchetypeCatalog=local \ + -DinteractiveMode=false + if [[ "$?" -ne 0 ]]; then + exit 1 + fi + cd ${ARCHETYPE_TEST_DIR}/${PROJ_NAME} && mvn -q clean package -Pshaded-jar + if [[ "$?" -ne 0 ]]; then + exit 1 + fi + cd ${ARCHETYPE_TEST_DIR}/${PROJ_NAME} && mvn -q clean package + if [[ "$?" -ne 0 ]]; then + exit 1 + fi + cd ${ARCHETYPE_TEST_DIR}/${PROJ_NAME} && gradle -q wrapper + if [[ "$?" -ne 0 ]]; then + exit 1 + fi + cd ${ARCHETYPE_TEST_DIR}/${PROJ_NAME} && ./gradlew -q clean build + if [[ "$?" -ne 0 ]]; then + exit 1 + fi +} + +function sample { + 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 +cd ${WORKING_DIR}/ && mvn -q --non-recursive clean install + +install ${FRAMEWORK} ${EXTRA_PARAMS} +if [[ "$RUN_ARCHETYPE" = true ]] ; then + archetype ${FRAMEWORK} +fi +if [[ "$RUN_SAMPLES" = true ]] ; then + sample ${FRAMEWORK} +fi \ No newline at end of file diff --git a/owasp-suppression.xml b/owasp-suppression.xml index fe70f749b..0f16cbd34 100644 --- a/owasp-suppression.xml +++ b/owasp-suppression.xml @@ -17,24 +17,11 @@ ~ specific language governing permissions and limitations ~ under the License. --> - + - - cpe:/a:amazon_aws_project:amazon_aws:7.x-1.2::~~~drupal~~ + + ^pkg:maven/com.fasterxml.jackson.core/jackson-databind@.*$ + CVE-2023-35116 - - - cpe:/a:restful_web_services_project:restful_web_services:7.x-2.1::~~~drupal~~ - - - - - - - cpe:/a:slf4j:slf4j:1.8.0 - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 74903bc77..ebc634fbb 100644 --- a/pom.xml +++ b/pom.xml @@ -4,36 +4,69 @@ com.amazonaws.serverless aws-serverless-java-container pom - 1.4-SNAPSHOT + 2.1.5-SNAPSHOT AWS Serverless Java container - - - - org.sonatype.oss - oss-parent - 7 - + A Java framework to run Spring, Spring Boot, Jersey, Spark, and Struts applications inside AWS Lambda + https://github.com/aws/serverless-java-container + + GitHub Issues + https://github.com/aws/serverless-java-container/issues + + + + + amazonwebservices + Amazon Web Services + https://aws.amazon.com + + developer + + + aws-serverless-java-container-core aws-serverless-java-container-jersey - aws-serverless-java-container-spark aws-serverless-java-container-spring - aws-serverless-java-container-struts2 - aws-serverless-struts2-archetype + aws-serverless-java-container-springboot3 aws-serverless-jersey-archetype - aws-serverless-spark-archetype aws-serverless-spring-archetype - aws-serverless-springboot-archetype - aws-serverless-springboot2-archetype + aws-serverless-springboot3-archetype - https://github.com/awslabs/aws-serverless-java-container.git - scm:git:git@github.com:awslabs/aws-serverless-java-container.git - scm:git:git@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 + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://aws.oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://aws.oss.sonatype.org/content/repositories/snapshots/ + + + sonatype-nexus-staging + Nexus Release Repository + https://aws.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + The Apache Software License, Version 2.0 @@ -43,64 +76,109 @@ - 0.5 - 4.0.1 + 0.7 + 12.1.1 + 2.19.1 + 2.0.17 + 5.12.2 + 5.19.0 + 1.3 + UTF-8 - - junit - junit - 4.12 + 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 - 1.8.0-beta2 + ${slf4j.version} - org.slf4j slf4j-simple - 1.8.0-beta2 + ${slf4j.version} + test + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.5 test - org.mockito - mockito-all - 1.10.19 + mockito-core + ${mockito.version} test - - com.google.code.findbugs - annotations - 3.0.1 + org.hamcrest + hamcrest-all + ${hamcrest.version} + test + + + + 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 + @@ -112,6 +190,7 @@ org.apache.maven.plugins maven-compiler-plugin + 3.14.0 1.8 1.8 @@ -122,37 +201,98 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.4 + 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 - false true + chore: release - + true + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.9.3.0 + + + Max + + Low + + High + + true + + ${project.build.directory}/spotbugs + ${project.parent.basedir}/spotbugs-excludeFilter.xml + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.14.0 + + + + org.jacoco + jacoco-maven-plugin + 0.8.13 + - release-sign-artifacts - - - performRelease - true - - + serverless-java-container-release org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.2.7 sign-artifacts @@ -163,6 +303,31 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + + jar-no-fork + + + + diff --git a/samples/jersey/pet-store/README.md b/samples/jersey/pet-store/README.md index bfb441648..d85d56284 100644 --- a/samples/jersey/pet-store/README.md +++ b/samples/jersey/pet-store/README.md @@ -1,62 +1,36 @@ # Serverless Jersey example -A basic pet store written with the [Jersey framework](https://jersey.java.net/). The `LambdaHandler` object is the main entry point for Lambda. +A basic pet store written with the [Jersey framework](https://jersey.java.net/). The `StreamLambdaHandler` object is the main entry point for Lambda. -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition. -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. +## Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) -In a shell, navigate to the sample's folder and use maven to build a deployable jar. +## Deployment +In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package ``` -$ mvn package +$ sam build ``` -This command should generate a `serverless-jersey-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. +This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen ``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name +$ sam deploy --guided ``` -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessJerseySample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `JerseyPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessJerseySample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written in jersey with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "JerseyPetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "JerseySample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +--------------------------------------------------------------------------------------------------------- -Copy the `OutputValue` into a browser to test a first request. \ No newline at end of file +$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +``` \ No newline at end of file diff --git a/samples/jersey/pet-store/build.gradle b/samples/jersey/pet-store/build.gradle index 3e9e97628..0194402b2 100644 --- a/samples/jersey/pet-store/build.gradle +++ b/samples/jersey/pet-store/build.gradle @@ -6,20 +6,18 @@ repositories { } dependencies { - compile ( - 'com.amazonaws:aws-lambda-java-core:1.2.0', - 'com.amazonaws.serverless:aws-serverless-java-container-jersey:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', - 'io.symphonia:lambda-logging:1.0.1' + implementation ( + 'com.amazonaws.serverless:aws-serverless-java-container-jersey:[2.0-SNAPSHOT,)', + 'com.fasterxml.jackson.core:jackson-databind:2.19.1', ) - compile("org.glassfish.jersey.media:jersey-media-json-jackson:2.27") { + 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" } - compile("org.glassfish.jersey.inject:jersey-hk2:2.27") { + implementation("org.glassfish.jersey.inject:jersey-hk2:3.1.10") { exclude group: 'javax.inject', module: "javax.inject" } } diff --git a/samples/jersey/pet-store/pom.xml b/samples/jersey/pet-store/pom.xml index daf6b67c0..aa3e1caee 100644 --- a/samples/jersey/pet-store/pom.xml +++ b/samples/jersey/pet-store/pom.xml @@ -6,13 +6,13 @@ com.amazonaws.serverless.sample serverless-jersey-example - 1.0-SNAPSHOT + 2.0-SNAPSHOT Jersey example for the aws-serverless-java-container library Simple pet store written in Jersey https://aws.amazon.com/lambda/ - https://github.com/awslabs/aws-serverless-java-container.git + https://github.com/aws/serverless-java-container.git @@ -26,20 +26,15 @@ 1.8 1.8 - 2.27 + 3.1.10 + 2.19.1 com.amazonaws.serverless aws-serverless-java-container-jersey - [0.1,) - - - - com.amazonaws - aws-lambda-java-core - 1.2.0 + [2.0-SNAPSHOT,) @@ -77,14 +72,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.8 - - - - - io.symphonia - lambda-logging - 1.0.3 + ${jackson.version} @@ -97,7 +85,7 @@ org.apache.maven.plugins maven-shade-plugin - 2.3 + 3.6.0 false @@ -124,7 +112,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.1.1 + 3.4.2 default-jar @@ -132,11 +120,19 @@ + + org.apache.maven.plugins + maven-install-plugin + 3.1.4 + + true + + org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.8.1 copy-dependencies @@ -154,7 +150,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.1.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/src/main/java/com/amazonaws/serverless/sample/jersey/StreamLambdaHandler.java b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/StreamLambdaHandler.java index 20c6d534d..9701a2c37 100644 --- a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/StreamLambdaHandler.java +++ b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/StreamLambdaHandler.java @@ -11,6 +11,7 @@ import org.glassfish.jersey.jackson.JacksonFeature; import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.ServerProperties; import java.io.IOException; import java.io.InputStream; @@ -19,6 +20,13 @@ public class StreamLambdaHandler implements RequestStreamHandler { private static final ResourceConfig jerseyApplication = new ResourceConfig() + // properties to speed up Jersey start time + .property(ServerProperties.FEATURE_AUTO_DISCOVERY_DISABLE,true) + .property(ServerProperties.WADL_FEATURE_DISABLE,true) + .property(ServerProperties.METAINF_SERVICES_LOOKUP_DISABLE,true) + .property(ServerProperties.BV_FEATURE_DISABLE,true) + .property(ServerProperties.JSON_PROCESSING_FEATURE_DISABLE,true) + .property(ServerProperties.MOXY_JSON_FEATURE_DISABLE,true) .packages("com.amazonaws.serverless.sample.jersey") .register(JacksonFeature.class); private static final JerseyLambdaContainerHandler handler diff --git a/samples/jersey/pet-store/sam.yaml b/samples/jersey/pet-store/template.yml similarity index 69% rename from samples/jersey/pet-store/sam.yaml rename to samples/jersey/pet-store/template.yml index 7ca060dae..f16d04a7a 100644 --- a/samples/jersey/pet-store/sam.yaml +++ b/samples/jersey/pet-store/template.yml @@ -12,21 +12,21 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: com.amazonaws.serverless.sample.jersey.StreamLambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/serverless-jersey-example-1.0-SNAPSHOT-lambda-package.zip + Runtime: java21 + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 20 Events: - GetResource: - Type: Api + HttpApiEvent: + Type: HttpApi Properties: - Path: /{proxy+} - Method: any + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' Outputs: JerseyPetStoreApi: Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' Export: Name: JerseyPetStoreApi diff --git a/samples/spark/pet-store/README.md b/samples/spark/pet-store/README.md deleted file mode 100644 index 5d3e34d5a..000000000 --- a/samples/spark/pet-store/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Serverless Spark example -A basic pet store written with the [Spark framework](http://sparkjava.com/). The `LambdaHandler` object is the main entry point for Lambda. - -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition - -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. - -In a shell, navigate to the sample's folder and use maven to build a deployable jar. -``` -$ mvn package -``` - -This command should generate a `serverless-spark-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` - -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSparkSample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SparkPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSparkSample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/SparkSample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written with spark with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "PetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "SparkSample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - -``` - -Copy the `OutputValue` into a browser to test a first request. diff --git a/samples/spark/pet-store/build.gradle b/samples/spark/pet-store/build.gradle deleted file mode 100644 index 681dd4df2..000000000 --- a/samples/spark/pet-store/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -apply plugin: 'java' - -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - compile ( - 'com.sparkjava:spark-core:2.8.0', - 'com.amazonaws.serverless:aws-serverless-java-container-spark:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', - 'io.symphonia:lambda-logging:1.0.1' - ) -} - -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/sam.yaml b/samples/spark/pet-store/sam.yaml deleted file mode 100644 index b16086533..000000000 --- a/samples/spark/pet-store/sam.yaml +++ /dev/null @@ -1,32 +0,0 @@ -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 - -Resources: - PetStoreFunction: - Type: AWS::Serverless::Function - Properties: - Handler: com.amazonaws.serverless.sample.spark.StreamLambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/serverless-spark-example-1.0-SNAPSHOT-lambda-package.zip - MemorySize: 512 - Policies: AWSLambdaBasicExecutionRole - Timeout: 20 - Events: - GetResource: - Type: Api - Properties: - Path: /{proxy+} - Method: any - -Outputs: - SparkPetStoreApi: - Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' - Export: - Name: SparkPetStoreApi diff --git a/samples/spark/pet-store/src/assembly/bin.xml b/samples/spark/pet-store/src/assembly/bin.xml deleted file mode 100644 index fcb935036..000000000 --- a/samples/spark/pet-store/src/assembly/bin.xml +++ /dev/null @@ -1,32 +0,0 @@ - - lambda-package - - zip - - false - - - - ${project.build.directory}${file.separator}lib - - websocket* - jetty-http* - jetty-client* - jetty-webapp* - jetty-xml* - jetty-io* - - lib - - - - ${project.build.directory}${file.separator}classes - - ** - - ${file.separator} - - - \ No newline at end of file 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/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/StreamLambdaHandler.java b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/StreamLambdaHandler.java deleted file mode 100644 index 40ace9d33..000000000 --- a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/StreamLambdaHandler.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.amazonaws.serverless.sample.spark; - - -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.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; - -import spark.Spark; - -import javax.servlet.DispatcherType; -import javax.servlet.FilterRegistration; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.EnumSet; - - -public class StreamLambdaHandler implements RequestStreamHandler { - private static SparkLambdaContainerHandler handler; - static { - try { - handler = SparkLambdaContainerHandler.getAwsProxyHandler(); - SparkResources.defineResources(); - Spark.awaitInitialization(); - - // 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, "/*"); - }); - } 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); - } - } - - public StreamLambdaHandler() { - // we enable the timer for debugging. This SHOULD NOT be enabled in production. - Timer.enable(); - } - - @Override - public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) - throws IOException { - handler.proxyStream(inputStream, outputStream, context); - } -} diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/PetData.java b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/PetData.java deleted file mode 100644 index ba9c47db6..000000000 --- a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/PetData.java +++ /dev/null @@ -1,111 +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.model; - -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; - -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 static List getBreeds() { - return breeds; - } - - public static List getNames() { - return names; - } - - public static String getRandomBreed() { - return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1)); - } - - public static String getRandomName() { - return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1)); - } - - public static 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/spring/pet-store/README.md b/samples/spring/pet-store/README.md index 17592941b..5637b6b69 100644 --- a/samples/spring/pet-store/README.md +++ b/samples/spring/pet-store/README.md @@ -1,62 +1,36 @@ # Serverless Spring example -A basic pet store written with the [Spring framework](https://projects.spring.io/spring-framework/). The `LambdaHandler` object is the main entry point for Lambda. +A basic pet store written with the [Spring framework](https://projects.spring.io/spring-framework/). The `StreamLambdaHandler` object is the main entry point for Lambda. -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition. -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. +## Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) -In a shell, navigate to the sample's folder and use maven to build a deployable jar. +## Deployment +In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package ``` -$ mvn package +$ sam build ``` -This command should generate a `serverless-spring-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. +This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen ``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name +$ sam deploy --guided ``` -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringSample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SparkPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringSample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written with spark with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "PetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "JerseySample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL ``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +--------------------------------------------------------------------------------------------------------- -Copy the `OutputValue` into a browser to test a first request. +$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +``` \ No newline at end of file diff --git a/samples/spring/pet-store/build.gradle b/samples/spring/pet-store/build.gradle index 73e18d968..d2fecf23e 100644 --- a/samples/spring/pet-store/build.gradle +++ b/samples/spring/pet-store/build.gradle @@ -6,15 +6,15 @@ repositories { } dependencies { - compile ( - 'org.springframework:spring-webmvc:5.1.1.RELEASE', - 'org.springframework:spring-context:5.1.1.RELEASE', - 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', - 'org.apache.logging.log4j:log4j-core:2.8.2', - 'org.apache.logging.log4j:log4j-api:2.8.2', - 'org.apache.logging.log4j:log4j-slf4j-impl:2.8.2', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', - 'com.amazonaws:aws-lambda-java-log4j2:1.1.0', + implementation ( + '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 1f60ce0b0..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.1.1.RELEASE - 4.12 - 2.8.2 + 6.2.10 + 2.24.3 + 17 + 17 com.amazonaws.serverless aws-serverless-java-container-spring - [0.1,) - - - - com.amazonaws - aws-lambda-java-core - 1.2.0 + [2.0-SNAPSHOT,) @@ -90,14 +83,7 @@ com.amazonaws aws-lambda-java-log4j2 - 1.1.0 - - - - junit - junit - ${junit.version} - test + 1.6.0 @@ -109,7 +95,7 @@ org.apache.maven.plugins maven-shade-plugin - 2.3 + 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.1.1 + 3.4.2 default-jar @@ -156,11 +141,19 @@ + + org.apache.maven.plugins + maven-install-plugin + 3.1.4 + + true + + org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.8.1 copy-dependencies @@ -178,7 +171,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.1.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/sam.yaml b/samples/spring/pet-store/template.yml similarity index 69% rename from samples/spring/pet-store/sam.yaml rename to samples/spring/pet-store/template.yml index 6a6f07e47..34cecbca2 100644 --- a/samples/spring/pet-store/sam.yaml +++ b/samples/spring/pet-store/template.yml @@ -12,21 +12,21 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: com.amazonaws.serverless.sample.spring.StreamLambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/serverless-spring-example-1.0-SNAPSHOT-lambda-package.zip + Runtime: java21 + CodeUri: . MemorySize: 512 Policies: AWSLambdaBasicExecutionRole Timeout: 30 Events: - GetResource: - Type: Api + HttpApiEvent: + Type: HttpApi Properties: - Path: /{proxy+} - Method: any + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' Outputs: SpringPetStoreApi: Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' Export: Name: SpringPetStoreApi diff --git a/samples/springboot/pet-store/README.md b/samples/springboot/pet-store/README.md deleted file mode 100644 index ae9100cb3..000000000 --- a/samples/springboot/pet-store/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Serverless Spring Boot example -A basic pet store written with the [Spring Boot framework](https://projects.spring.io/spring-boot/). The `LambdaHandler` object is the main entry point for Lambda. - -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition - -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. - -In a shell, navigate to the sample's folder and use maven to build a deployable jar. -``` -$ mvn package -``` - -This command should generate a `serverless-spring-boot-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` - -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringBootSample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SpringBootPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringBootSample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written with spark with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "SpringBootPetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "JerseySample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - -``` - -Copy the `OutputValue` into a browser to test a first request. diff --git a/samples/springboot/pet-store/build.gradle b/samples/springboot/pet-store/build.gradle deleted file mode 100644 index 1c5e5d489..000000000 --- a/samples/springboot/pet-store/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -plugins { - id 'org.springframework.boot' version '1.5.17.RELEASE' -} -apply plugin: 'java' - -repositories { - jcenter() - mavenLocal() - mavenCentral() -} - -dependencies { - compile ( - 'org.springframework.boot:spring-boot-starter-web', - 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', - 'io.symphonia:lambda-logging:1.0.1' - ) - testCompile("junit:junit") -} - -task buildZip(type: Zip) { - from compileJava - from processResources - into('lib') { - from(configurations.compileClasspath) { - exclude 'tomcat-embed-*' - } - } -} - -build.dependsOn buildZip diff --git a/samples/springboot/pet-store/sam.yaml b/samples/springboot/pet-store/sam.yaml deleted file mode 100644 index 23e143a95..000000000 --- a/samples/springboot/pet-store/sam.yaml +++ /dev/null @@ -1,32 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: Example Pet Store API written with SpringBoot with the aws-serverless-java-container library - -Globals: - Api: - # API Gateway regional endpoints - EndpointConfiguration: REGIONAL - -Resources: - PetStoreFunction: - Type: AWS::Serverless::Function - Properties: - Handler: com.amazonaws.serverless.sample.springboot.StreamLambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/serverless-spring-boot-example-1.0-SNAPSHOT-lambda-package.zip - MemorySize: 1512 - Policies: AWSLambdaBasicExecutionRole - Timeout: 60 - Events: - GetResource: - Type: Api - Properties: - Path: /{proxy+} - Method: any - -Outputs: - SpringBootPetStoreApi: - Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' - Export: - Name: SpringBootPetStoreApi diff --git a/samples/springboot2/pet-store/README.md b/samples/springboot2/pet-store/README.md deleted file mode 100644 index 5f8edb68b..000000000 --- a/samples/springboot2/pet-store/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Serverless Spring Boot 2 example -A basic pet store written with the [Spring Boot 2 framework](https://projects.spring.io/spring-boot/). The `LambdaHandler` object is the main entry point for Lambda. - -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition - -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. - -In a shell, navigate to the sample's folder and use maven to build a deployable jar. -``` -$ mvn package -``` - -This command should generate a `serverless-spring-boot-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` - -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringBootSample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SpringBootPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringBootSample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written with spark with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "SpringBootPetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "JerseySample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - -``` - -Copy the `OutputValue` into a browser to test a first request. 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 51% rename from samples/springboot2/pet-store/build.gradle rename to samples/springboot3/alt-pet-store/build.gradle index 8ad81f122..d2c99b907 100644 --- a/samples/springboot2/pet-store/build.gradle +++ b/samples/springboot3/alt-pet-store/build.gradle @@ -1,21 +1,19 @@ -plugins { - id 'org.springframework.boot' version '2.1.1.RELEASE' -} apply plugin: 'java' repositories { - jcenter() mavenLocal() mavenCentral() + maven {url "https://repo.spring.io/milestone"} + maven {url "https://repo.spring.io/snapshot"} } dependencies { - compile ( - 'org.springframework.boot:spring-boot-starter-web:2.1.1.RELEASE', - 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', - 'io.symphonia:lambda-logging:1.0.1' + implementation ( + 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-springboot3:[2.0-SNAPSHOT,)', ) - testCompile("junit:junit") } task buildZip(type: Zip) { diff --git a/samples/springboot2/pet-store/pom.xml b/samples/springboot3/alt-pet-store/pom.xml similarity index 77% rename from samples/springboot2/pet-store/pom.xml rename to samples/springboot3/alt-pet-store/pom.xml index b9d47fc1a..64cbb083c 100644 --- a/samples/springboot2/pet-store/pom.xml +++ b/samples/springboot3/alt-pet-store/pom.xml @@ -4,37 +4,40 @@ 4.0.0 com.amazonaws.serverless.sample - serverless-springboot2-example - 1.0-SNAPSHOT + 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/ org.springframework.boot spring-boot-starter-parent - 2.1.1.RELEASE + 3.5.0 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + - 1.8 - 1.8 + 17 org.springframework.boot - spring-boot-starter-web + spring-boot-starter com.amazonaws.serverless - aws-serverless-java-container-spring - [0.1,) - - - - - io.symphonia - lambda-logging - 1.0.1 + aws-serverless-java-container-springboot3 + [2.2.0-SNAPSHOT,),[2.1.1,) @@ -46,7 +49,7 @@ org.apache.maven.plugins maven-shade-plugin - 2.3 + 3.6.0 false @@ -80,7 +83,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.1.1 + 3.4.2 default-jar @@ -88,11 +91,19 @@ + + org.apache.maven.plugins + maven-install-plugin + 3.1.4 + + true + + org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.8.1 copy-dependencies @@ -110,7 +121,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.1.0 + 3.7.1 zip-assembly diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml b/samples/springboot3/alt-pet-store/src/assembly/bin.xml similarity index 100% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/assembly/bin.xml rename to samples/springboot3/alt-pet-store/src/assembly/bin.xml diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/Application.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java similarity index 61% rename from aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/Application.java rename to samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java index 7351392ff..428d67267 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/Application.java +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java @@ -1,34 +1,28 @@ -#macro(loggingOff) - logging.level.root:OFF -#end -#set($logging = "#loggingOff()") -#set($logging = $logging.replaceAll("\n", "").trim()) -package ${groupId}; +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.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.web.servlet.HandlerAdapter; -import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import com.amazonaws.serverless.sample.springboot3.controller.PetsController; +import com.amazonaws.serverless.sample.springboot3.filter.CognitoIdentityFilter; -import ${groupId}.controller.PingController; +import jakarta.servlet.Filter; @SpringBootApplication -// We use direct @Import instead of @ComponentScan to speed up cold starts -// @ComponentScan(basePackages = "${groupId}.controller") -@Import({ PingController.class }) -public class Application extends SpringBootServletInitializer { +@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 @@ -46,6 +40,11 @@ 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); } 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 86% 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 3097bb80b..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"; @@ -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/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/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/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/model/Error.java rename to samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java index 74d7ecbfe..320f21582 100644 --- a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/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.springboot.model; +package com.amazonaws.serverless.sample.springboot3.model; public class Error { private String message; diff --git a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/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/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/model/Pet.java rename to samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java index 1b07530cd..4f0c4ba8e 100644 --- a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/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.springboot.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/springboot3/graphql-pet-store/README.md b/samples/springboot3/graphql-pet-store/README.md new file mode 100644 index 000000000..e5bfad120 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/README.md @@ -0,0 +1,38 @@ +# 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. + +## 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` to make a call to the URL + +``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/graphQl +--------------------------------------------------------------------------------------------------------- + +$ 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/spark/pet-store/pom.xml b/samples/springboot3/graphql-pet-store/pom.xml similarity index 66% rename from samples/spark/pet-store/pom.xml rename to samples/springboot3/graphql-pet-store/pom.xml index 09502efd0..23ac0bae6 100644 --- a/samples/spark/pet-store/pom.xml +++ b/samples/springboot3/graphql-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 + 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/ - - https://github.com/awslabs/aws-serverless-java-container.git - + + org.springframework.boot + spring-boot-starter-parent + 3.5.0 + @@ -24,38 +25,33 @@ - 1.8 - 1.8 - 2.9.8 - 2.8.0 + 17 - com.amazonaws.serverless - aws-serverless-java-container-spark - [0.2,) + org.springframework.boot + spring-boot-starter-graphql - - - 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} + org.springframework.graphql + spring-graphql-test + test - - - io.symphonia - lambda-logging - 1.0.1 + com.amazonaws.serverless + aws-serverless-java-container-springboot3 + [2.0.0-SNAPSHOT,),[2.0.0-M1,) @@ -67,7 +63,7 @@ org.apache.maven.plugins maven-shade-plugin - 2.3 + 3.6.0 false @@ -80,15 +76,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 +97,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.1.1 + 3.4.2 default-jar @@ -117,11 +105,19 @@ + + org.apache.maven.plugins + maven-install-plugin + 3.1.4 + + true + + org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.8.1 copy-dependencies @@ -139,7 +135,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.1.0 + 3.7.1 zip-assembly @@ -162,4 +158,5 @@ + diff --git a/samples/springboot2/pet-store/src/assembly/bin.xml b/samples/springboot3/graphql-pet-store/src/assembly/bin.xml similarity index 98% rename from samples/springboot2/pet-store/src/assembly/bin.xml rename to samples/springboot3/graphql-pet-store/src/assembly/bin.xml index 1e085057d..efc312c25 100644 --- a/samples/springboot2/pet-store/src/assembly/bin.xml +++ b/samples/springboot3/graphql-pet-store/src/assembly/bin.xml @@ -24,4 +24,4 @@ ${file.separator} - \ No newline at end of file + diff --git a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/Application.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java similarity index 51% rename from samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/Application.java rename to samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java index 3019b05e7..9cf0ea610 100644 --- a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/Application.java +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java @@ -1,27 +1,21 @@ -package com.amazonaws.serverless.sample.springboot; +package com.amazonaws.serverless.sample.springboot3; -import com.amazonaws.serverless.sample.springboot.controller.PetsController; +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.boot.web.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.web.servlet.HandlerAdapter; -import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - @SpringBootApplication @Import({ PetsController.class }) -public class Application extends SpringBootServletInitializer { +public class Application { // silence console logging @Value("${logging.level.root:OFF}") @@ -43,27 +37,7 @@ public HandlerAdapter handlerAdapter() { return new RequestMappingHandlerAdapter(); } - /* - * optimization - avoids creating default exception resolvers; not required as the serverless container handles - * all exceptions - * - * By default, an ExceptionHandlerExceptionResolver is created which creates many dependent object, including - * an expensive ObjectMapper instance. - * - * To enable custom @ControllerAdvice classes remove this bean. - */ - @Bean - public HandlerExceptionResolver handlerExceptionResolver() { - return new HandlerExceptionResolver() { - - @Override - public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { - return null; - } - }; - } - public static void main(String[] args) { SpringApplication.run(Application.class, args); } -} \ No newline at end of file +} 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 90% 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 cecee6db4..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; 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/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/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/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/filter/CognitoIdentityFilter.java rename to samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java index 025f565dc..d6ccae765 100644 --- a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/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.springboot.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.springboot.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/springboot2/pet-store/src/main/resources/logback.xml b/samples/springboot3/graphql-pet-store/src/main/resources/logback.xml similarity index 91% rename from samples/springboot2/pet-store/src/main/resources/logback.xml rename to samples/springboot3/graphql-pet-store/src/main/resources/logback.xml index 14a3a84fa..8ff988992 100644 --- a/samples/springboot2/pet-store/src/main/resources/logback.xml +++ b/samples/springboot3/graphql-pet-store/src/main/resources/logback.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/samples/springboot3/graphql-pet-store/template.yml b/samples/springboot3/graphql-pet-store/template.yml new file mode 100644 index 000000000..ce5dcc6b1 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/template.yml @@ -0,0 +1,32 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Example Pet Store API written with SpringBoot3, Spring for GraphQl and the aws-serverless-java-container library + +Globals: + Api: + # API Gateway regional endpoints + EndpointConfiguration: REGIONAL + +Resources: + PetStoreFunction: + Type: AWS::Serverless::Function + Properties: + Handler: com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler::handleRequest + Runtime: java21 + CodeUri: . + MemorySize: 1024 + Policies: AWSLambdaBasicExecutionRole + Timeout: 60 + Events: + HttpApiEvent: + Type: HttpApi + Properties: + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' + +Outputs: + SpringBootPetStoreApi: + Description: URL for application + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/graphql' + Export: + 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/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/controller/PetsController.java b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java similarity index 80% rename from samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/controller/PetsController.java rename to samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java index 2680d111b..849286fec 100644 --- a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/controller/PetsController.java +++ b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java @@ -10,13 +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.springboot.controller; +package com.amazonaws.serverless.sample.springboot3.controller; -import com.amazonaws.serverless.sample.springboot.model.Pet; -import com.amazonaws.serverless.sample.springboot.model.PetData; - +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; @@ -24,6 +23,9 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; +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; @@ -32,8 +34,9 @@ @RestController @EnableWebMvc public class PetsController { - @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; } @@ -43,8 +46,9 @@ public Pet createPet(@RequestBody Pet newPet) { return dbPet; } - @RequestMapping(path = "/pets", method = RequestMethod.GET) + @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(); @@ -64,8 +68,9 @@ public Pet[] listPets(@RequestParam("limit") Optional limit, Principal return outputPets; } - @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET) + @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()); diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/filter/CognitoIdentityFilter.java b/samples/springboot3/pet-store-native/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/pet-store-native/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/pet-store-native/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/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/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/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/model/Error.java rename to samples/springboot3/pet-store-native/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/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.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/pet-store-native/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/pet-store-native/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/pet-store-native/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/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/model/PetData.java b/samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java similarity index 98% rename from samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/model/PetData.java rename to samples/springboot3/pet-store-native/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java index 05026fd3d..68ea3c18b 100644 --- a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/model/PetData.java +++ b/samples/springboot3/pet-store-native/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.springboot.model; +package com.amazonaws.serverless.sample.springboot3.model; import java.util.ArrayList; 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/springboot3/pet-store-native/template.yaml b/samples/springboot3/pet-store-native/template.yaml new file mode 100644 index 000000000..dc05e1be7 --- /dev/null +++ b/samples/springboot3/pet-store-native/template.yaml @@ -0,0 +1,34 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Serverless Java Container GraalVM +Resources: + ServerlessWebNativeFunction: + Type: AWS::Serverless::Function + Properties: + MemorySize: 512 + 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: + ServerlessWebNativeApi: + Description: URL for application + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' + Export: + Name: ServerlessWebNativeApi + \ No newline at end of file diff --git a/samples/springboot3/pet-store/README.md b/samples/springboot3/pet-store/README.md new file mode 100644 index 000000000..fb3fab3b0 --- /dev/null +++ b/samples/springboot3/pet-store/README.md @@ -0,0 +1,36 @@ +# 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. + +## 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 +``` \ No newline at end of file diff --git a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/build.gradle b/samples/springboot3/pet-store/build.gradle similarity index 51% rename from aws-serverless-springboot-archetype/src/main/resources/archetype-resources/build.gradle rename to samples/springboot3/pet-store/build.gradle index 88c7d62f7..653135db0 100644 --- a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/build.gradle +++ b/samples/springboot3/pet-store/build.gradle @@ -1,22 +1,19 @@ -plugins { - id 'org.springframework.boot' version '1.5.17.RELEASE' -} apply plugin: 'java' repositories { - jcenter() mavenLocal() mavenCentral() + maven {url "https://repo.spring.io/milestone"} + maven {url "https://repo.spring.io/snapshot"} } dependencies { - compile ( - 'org.springframework.boot:spring-boot-starter-web', - 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', - 'io.symphonia:lambda-logging:1.0.1' + implementation ( + 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,)', ) - - testCompile("junit:junit:4.12") } task buildZip(type: Zip) { diff --git a/samples/springboot/pet-store/pom.xml b/samples/springboot3/pet-store/pom.xml similarity index 75% rename from samples/springboot/pet-store/pom.xml rename to samples/springboot3/pet-store/pom.xml index 51f177e59..4126ead75 100644 --- a/samples/springboot/pet-store/pom.xml +++ b/samples/springboot3/pet-store/pom.xml @@ -4,37 +4,46 @@ 4.0.0 com.amazonaws.serverless.sample - serverless-spring-boot-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/ org.springframework.boot spring-boot-starter-parent - 1.5.17.RELEASE + 3.5.0 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + - 1.8 - 1.8 + 17 org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + com.amazonaws.serverless - aws-serverless-java-container-spring - [0.1,) - - - - - io.symphonia - lambda-logging - 1.0.1 + aws-serverless-java-container-springboot3 + [2.0.0-SNAPSHOT,),[2.0.0-M1,) @@ -46,7 +55,7 @@ org.apache.maven.plugins maven-shade-plugin - 2.3 + 3.6.0 false @@ -80,7 +89,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.1.1 + 3.4.2 default-jar @@ -88,11 +97,19 @@ + + org.apache.maven.plugins + maven-install-plugin + 3.1.4 + + true + + org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.8.1 copy-dependencies @@ -110,7 +127,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.1.0 + 3.7.1 zip-assembly @@ -133,4 +150,5 @@ + diff --git a/samples/springboot/pet-store/src/assembly/bin.xml b/samples/springboot3/pet-store/src/assembly/bin.xml similarity index 100% rename from samples/springboot/pet-store/src/assembly/bin.xml rename to samples/springboot3/pet-store/src/assembly/bin.xml 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 84% 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 e49b1f6d2..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,13 +15,13 @@ 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 @Import({ PetsController.class }) -public class Application extends SpringBootServletInitializer { +public class Application { // silence console logging @Value("${logging.level.root:OFF}") diff --git a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/StreamLambdaHandler.java b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java similarity index 89% rename from samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/StreamLambdaHandler.java rename to samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java index 38e6dae8c..a65c6f1ec 100644 --- a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/StreamLambdaHandler.java +++ b/samples/springboot3/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java @@ -1,4 +1,4 @@ -package com.amazonaws.serverless.sample.springboot; +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.springboot.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; 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/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Error.java b/samples/springboot3/pet-store/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/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/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/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/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/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/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/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/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/springboot/pet-store/src/main/resources/logback.xml b/samples/springboot3/pet-store/src/main/resources/logback.xml similarity index 100% rename from samples/springboot/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/sam.yaml b/samples/springboot3/pet-store/template.yml similarity index 63% rename from samples/springboot2/pet-store/sam.yaml rename to samples/springboot3/pet-store/template.yml index c00d45c3f..a8474349b 100644 --- a/samples/springboot2/pet-store/sam.yaml +++ b/samples/springboot3/pet-store/template.yml @@ -11,22 +11,22 @@ Resources: PetStoreFunction: Type: AWS::Serverless::Function Properties: - Handler: com.amazonaws.serverless.sample.springboot2.StreamLambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/serverless-springboot2-example-1.0-SNAPSHOT-lambda-package.zip + Handler: com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler::handleRequest + Runtime: java21 + CodeUri: . MemorySize: 1512 Policies: AWSLambdaBasicExecutionRole Timeout: 60 Events: - GetResource: - Type: Api + HttpApiEvent: + Type: HttpApi Properties: - Path: /{proxy+} - Method: any + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' Outputs: SpringBootPetStoreApi: Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' Export: Name: SpringBootPetStoreApi diff --git a/samples/struts/pet-store/README.md b/samples/struts/pet-store/README.md deleted file mode 100644 index 9203dd896..000000000 --- a/samples/struts/pet-store/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Serverless Struts2 example -A basic pet store written with the [Apache Struts framework](https://struts.apache.org). The `Struts2LambdaHandler` object provided by the `aws-serverless-java-container-struts2` is the main entry point for Lambda. - -The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition - -## Installation -To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. - -In a shell, navigate to the sample's folder and use maven to build a deployable jar. -``` -$ mvn package -``` - -This command should generate a `serverless-struts-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the zip file, we can use the AWS CLI to package the template for deployment. - -You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: - -``` -$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket -Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) -Successfully packaged artifacts and wrote output template to file output-sam.yaml. -Execute the following command to deploy the packaged template -aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name -``` - -As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. - -``` -$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringSample --capabilities CAPABILITY_IAM -``` - -Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SparkPetStoreApi` key of the `Outputs` property: - -``` -$ aws cloudformation describe-stacks --stack-name ServerlessSpringSample -{ - "Stacks": [ - { - "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", - "Description": "Example Pet Store API written with Apache Struts with the aws-serverless-java-container library", - "Tags": [], - "Outputs": [ - { - "Description": "URL for application", - "OutputKey": "PetStoreApi", - "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" - } - ], - "CreationTime": "2016-12-13T22:59:31.552Z", - "Capabilities": [ - "CAPABILITY_IAM" - ], - "StackName": "StrutsSample", - "NotificationARNs": [], - "StackStatus": "UPDATE_COMPLETE" - } - ] -} - -``` - -Copy the `OutputValue` into a browser to test a first request. diff --git a/samples/struts/pet-store/build.gradle b/samples/struts/pet-store/build.gradle deleted file mode 100644 index 829ca79bb..000000000 --- a/samples/struts/pet-store/build.gradle +++ /dev/null @@ -1,33 +0,0 @@ -apply plugin: 'java' - -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - compile ( - 'com.amazonaws.serverless:aws-serverless-java-container-struts2:[1.0,)', - 'org.apache.struts:struts2-convention-plugin:2.5.20', - 'org.apache.struts:struts2-rest-plugin:2.5.20', - 'org.apache.struts:struts2-bean-validation-plugin:2.5.20', - 'org.apache.struts:struts2-junit-plugin:2.5.20', - 'com.jgeppert.struts2:struts2-aws-lambda-support-plugin:1.1.0', - 'org.hibernate:hibernate-validator:4.3.2.Final', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', - 'org.apache.logging.log4j:log4j-core:2.8.2', - 'org.apache.logging.log4j:log4j-api:2.8.2', - 'org.apache.logging.log4j:log4j-slf4j-impl:2.8.2', - 'com.amazonaws:aws-lambda-java-log4j2:1.1.0', - ) -} - -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 b104bd716..000000000 --- a/samples/struts/pet-store/pom.xml +++ /dev/null @@ -1,167 +0,0 @@ - - - 4.0.0 - - com.amazonaws.serverless.sample - serverless-struts-example - 1.0-SNAPSHOT - Struts2 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 - 2.5.20 - 2.9.8 - 4.12 - 2.11.1 - - - - - com.amazonaws.serverless - aws-serverless-java-container-struts2 - [0.1,) - - - - com.amazonaws - aws-lambda-java-core - 1.2.0 - - - - org.apache.struts - struts2-convention-plugin - ${struts2.version} - - - - org.apache.struts - struts2-rest-plugin - ${struts2.version} - - - - org.apache.struts - struts2-bean-validation-plugin - ${struts2.version} - - - - org.apache.struts - struts2-junit-plugin - ${struts2.version} - test - - - - - com.jgeppert.struts2 - struts2-aws-lambda-support-plugin - 1.1.0 - - - - - org.hibernate - hibernate-validator - 4.3.2.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} - - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - - - - org.apache.logging.log4j - log4j-api - ${log4j.version} - - - - org.apache.logging.log4j - log4j-slf4j-impl - ${log4j.version} - - - - com.amazonaws - aws-lambda-java-log4j2 - 1.1.0 - - - - junit - junit - ${junit.version} - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.1.0 - - - src/main/assembly/dist.xml - - - - - lambda - package - - single - - - - - - - - diff --git a/samples/struts/pet-store/sam.yaml b/samples/struts/pet-store/sam.yaml deleted file mode 100644 index c44aa3201..000000000 --- a/samples/struts/pet-store/sam.yaml +++ /dev/null @@ -1,32 +0,0 @@ -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 - -Globals: - Api: - # API Gateway regional endpoints - EndpointConfiguration: REGIONAL - -Resources: - PetStoreFunction: - Type: AWS::Serverless::Function - Properties: - Handler: com.amazonaws.serverless.proxy.struts2.Struts2LambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/serverless-struts-example-1.0-SNAPSHOT-lambda.zip - MemorySize: 256 - Policies: AWSLambdaBasicExecutionRole - Timeout: 30 - Events: - GetResource: - Type: Api - Properties: - Path: /{proxy+} - Method: any - -Outputs: - SpringPetStoreApi: - Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' - Export: - Name: Struts2PetStoreApi 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 038370d3d..000000000 --- a/samples/struts/pet-store/src/main/resources/struts.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spotbugs-excludeFilter.xml b/spotbugs-excludeFilter.xml new file mode 100644 index 000000000..b26b1ad24 --- /dev/null +++ b/spotbugs-excludeFilter.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/travis.sh b/travis.sh deleted file mode 100755 index 13996f103..000000000 --- a/travis.sh +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env bash - -IFS=$'\n' - -echo "STARTING TRAVIS BUILD in $(pwd)" -cd $TRAVIS_BUILD_DIR -mvn install -if [[ "$?" -ne 0 ]]; then - exit 1 -fi -echo "COMPLETED MAIN FRAMEWORK INSTALL" - -echo "STARTING SAMPLES BUILD" -for SAMPLE in $(find samples -type d -maxdepth 2 -mindepth 2); -do - echo "BUILDING SAMPLE '$SAMPLE'" - cd $SAMPLE - mvn clean package - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - $GRADLE wrapper - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - ./gradlew wrapper --gradle-version 5.0 - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - ./gradlew clean build - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - - SAM_FILE="$TRAVIS_BUILD_DIR/$SAMPLE/sam.yaml" - if [[ -f "$SAM_FILE" ]]; then - TARGET_ZIP=$(cat $SAM_FILE | grep CodeUri | sed -e 's/^.*:\ //g') - if [[ ! -f "./$TARGET_ZIP" ]]; then - echo "COULD NOT FIND TARGET ZIP FILE $TARGET_ZIP for $SAMPLE" - exit 1 - fi - else - echo "COULD NOT FIND SAM FILE: '$TRAVIS_BUILD_DIR/$SAMPLE/sam.yaml'" - exit 1 - fi - - cd $TRAVIS_BUILD_DIR - echo "'$SAMPLE' BUILT SUCCESSFULLY" -done -echo "COMPLETED SAMPLES BUILD" - -cd $TRAVIS_BUILD_DIR -rm -rf tmp -echo "STARTING ARCHETYPE BUILD" -for ARCH in $(find . -name "*archetype" -type d); -do - echo "TESTING ARCHETYPE '$ARCH'" - ARCH_NAME=$(basename $ARCH) - TEST_PROJ="TEST-$ARCH_NAME" - mkdir tmp && cd tmp - mvn archetype:generate -DgroupId=my.service -DartifactId=$TEST_PROJ -Dversion=1.0-SNAPSHOT \ - -DarchetypeGroupId=com.amazonaws.serverless.archetypes \ - -DarchetypeArtifactId=$ARCH_NAME \ - -DarchetypeCatalog=local \ - -DinteractiveMode=false - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - cd ${TEST_PROJ} - mvn clean package -Pshaded-jar - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - mvn clean package - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - if [[ -f "$TRAVIS_BUILD_DIR/tmp/$TEST_PROJ/build.gradle" ]]; then - $GRADLE wrapper - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - ./gradlew wrapper --gradle-version 5.0 - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - ./gradlew clean build - if [[ "$?" -ne 0 ]]; then - exit 1 - fi - else - echo "GRADLE BUILD FILE NOT FOUND" - fi - - SAM_FILE="$TRAVIS_BUILD_DIR/tmp/$TEST_PROJ/sam.yaml" - if [[ -f "$SAM_FILE" ]]; then - TARGET_ZIP=$(cat $SAM_FILE | grep CodeUri | sed -e 's/^.*:\ //g') - if [[ ! -f "./$TARGET_ZIP" ]]; then - echo "COULD NOT FIND TARGET ZIP FILE $TARGET_ZIP for $ARCH" - exit 1 - fi - else - echo "COULD NOT FIND SAM FILE: '$TRAVIS_BUILD_DIR/$SAMPLE/sam.yaml'" - exit 1 - fi - - - echo "'$ARCH' BUILT SUCCESSFULLY" - cd $TRAVIS_BUILD_DIR - rm -rf tmp -done -echo "COMPLETED ARCHETYPE BUILD" \ No newline at end of file