diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml
index 2785cd3dc..0095c65a8 100644
--- a/.github/workflows/continuous-integration-workflow.yml
+++ b/.github/workflows/continuous-integration-workflow.yml
@@ -82,6 +82,18 @@ jobs:
- name: Build with Spring Boot 3.3.x
run: ./gha_build.sh springboot3 false false -Dspringboot.version=3.3.6 -Dspring.version=6.1.15 -Dspringsecurity.version=6.3.5 -Ddependency-check.skip=true
+ build_springboot4:
+ name: Build and test SpringBoot 4
+ 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 springboot4 true true
# temporarily disabled as Struts is not released at the moment
# build_struts2:
# name: Build and test Struts
diff --git a/README.md b/README.md
index 3f26e0c1b..b7da76ea2 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Currently the following versions are maintained:
|---------|--------|-----------------------------|-----------------|------------------------|----------------|---------------|
| 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: |
+| 3.x | [main](https://github.com/aws/serverless-java-container/tree/main) | 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)
diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml
index e9aa6bbec..0090d8518 100644
--- a/aws-serverless-java-container-core/pom.xml
+++ b/aws-serverless-java-container-core/pom.xml
@@ -6,18 +6,19 @@
AWS Serverless Java container support - Core
Allows Java applications written for a servlet container to run in AWS Lambda
https://aws.amazon.com/lambda
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
..
3.1.0
- 6.0.0
+ 6.1.0
+ 3.0.2
@@ -40,13 +41,13 @@
- com.fasterxml.jackson.core
+ tools.jackson.core
jackson-databind
${jackson.version}
- com.fasterxml.jackson.module
+ tools.jackson.module
jackson-module-afterburner
${jackson.version}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java
index 0ae00b4da..8e13bd799 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java
@@ -18,7 +18,7 @@
import com.amazonaws.serverless.proxy.model.ErrorModel;
import com.amazonaws.serverless.proxy.model.Headers;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import tools.jackson.core.JacksonException;
import jakarta.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -103,7 +103,7 @@ protected String getErrorJson(String message) {
try {
return LambdaContainerHandler.getObjectMapper().writeValueAsString(new ErrorModel(message));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
log.error("Could not produce error JSON", e);
return "{ \"message\": \"" + message + "\" }";
}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java
index ff978456b..86d4216a1 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java
@@ -19,12 +19,10 @@
import com.amazonaws.serverless.proxy.model.ContainerConfig;
import com.amazonaws.services.lambda.runtime.Context;
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.ObjectReader;
-import com.fasterxml.jackson.databind.ObjectWriter;
-import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ObjectReader;
+import tools.jackson.databind.ObjectWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -86,10 +84,9 @@ public abstract class LambdaContainerHandler {
return subject;
});
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
log.error("Error while attempting to parse JWT body for requestId: " + SecurityUtils.crlf(event.getRequestContext().getRequestId()), e);
return null;
}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java
index f5395a0bf..2e1d4827e 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java
@@ -28,6 +28,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.core.HttpHeaders;
+import java.nio.ByteBuffer;
import jakarta.ws.rs.core.MediaType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -150,6 +151,16 @@ public void sendRedirect(String s) throws IOException {
flushBuffer();
}
+ @Override
+ public void sendRedirect(String location, int sc, boolean clearBuffer) throws IOException {
+ setStatus(sc);
+ addHeader(HttpHeaders.LOCATION, location);
+ if (clearBuffer) {
+ resetBuffer();
+ }
+ flushBuffer();
+ }
+
@Override
public void setDateHeader(String s, long l) {
@@ -297,6 +308,25 @@ public void write(int b) throws IOException {
}
}
+ @Override
+ public void write(ByteBuffer b) throws IOException {
+ try {
+ if (b.hasArray()) {
+ bodyOutputStream.write(b.array(), b.arrayOffset() + b.position(), b.remaining());
+ b.position(b.limit());
+ } else {
+ byte[] buf = new byte[b.remaining()];
+ b.get(buf);
+ bodyOutputStream.write(buf);
+ }
+ } catch (Exception e) {
+ log.error("Cannot write to output stream", e);
+ if (listener != null) {
+ listener.onError(e);
+ }
+ }
+ }
+
@Override
public void flush() throws IOException {
flushBuffer();
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java
index 9c4b7971b..c2a257d34 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java
@@ -607,12 +607,22 @@ private List getHeaderValues(String key) {
if (request.getRequestSource() == RequestSource.API_GATEWAY) {
if ("referer".equals(key.toLowerCase(Locale.ENGLISH))) {
- values.add(request.getRequestContext().getIdentity().getCaller());
- return values;
+ if (request.getRequestContext() != null && request.getRequestContext().getIdentity() != null) {
+ String caller = request.getRequestContext().getIdentity().getCaller();
+ if (caller != null) {
+ values.add(caller);
+ return values;
+ }
+ }
}
if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) {
- values.add(request.getRequestContext().getIdentity().getUserAgent());
- return values;
+ if (request.getRequestContext() != null && request.getRequestContext().getIdentity() != null) {
+ String userAgent = request.getRequestContext().getIdentity().getUserAgent();
+ if (userAgent != null) {
+ values.add(userAgent);
+ return values;
+ }
+ }
}
}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java
index eb155a364..220ca602f 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletInputStream.java
@@ -20,6 +20,7 @@
import jakarta.servlet.ServletInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.ByteBuffer;
public class AwsServletInputStream extends ServletInputStream {
private static Logger log = LoggerFactory.getLogger(AwsServletInputStream.class);
@@ -73,4 +74,23 @@ public int read()
}
return readByte;
}
+
+ @Override
+ public int read(ByteBuffer b) throws IOException {
+ if (bodyStream == null || bodyStream instanceof NullInputStream) {
+ return -1;
+ }
+ if (!b.hasRemaining()) {
+ return 0;
+ }
+ byte[] buf = new byte[b.remaining()];
+ int bytesRead = bodyStream.read(buf);
+ if (bytesRead > 0) {
+ b.put(buf, 0, bytesRead);
+ }
+ if (bytesRead == -1) {
+ finished = true;
+ }
+ return bytesRead;
+ }
}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java
index 2cf6d77a6..8226be983 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/HttpApiV2AuthorizerMap.java
@@ -13,17 +13,16 @@
package com.amazonaws.serverless.proxy.model;
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
-import com.fasterxml.jackson.databind.ser.std.StdSerializer;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import tools.jackson.core.JsonGenerator;
+import tools.jackson.core.JsonParser;
+import tools.jackson.databind.DeserializationContext;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.SerializationContext;
+import tools.jackson.databind.annotation.JsonDeserialize;
+import tools.jackson.databind.annotation.JsonSerialize;
+import tools.jackson.databind.deser.std.StdDeserializer;
+import tools.jackson.databind.ser.std.StdSerializer;
+import tools.jackson.databind.type.TypeFactory;
import java.io.IOException;
import java.util.HashMap;
@@ -77,10 +76,9 @@ public HttpApiV2AuthorizerDeserializer() {
}
@Override
- public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
- throws IOException, JsonProcessingException {
+ public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
HttpApiV2AuthorizerMap map = new HttpApiV2AuthorizerMap();
- JsonNode node = jsonParser.getCodec().readTree(jsonParser);
+ JsonNode node = deserializationContext.readTree(jsonParser);
if (node.has(JWT_KEY)) {
HttpApiV2JwtAuthorizer authorizer = LambdaContainerHandler.getObjectMapper()
.treeToValue(node.get(JWT_KEY), HttpApiV2JwtAuthorizer.class);
@@ -88,7 +86,7 @@ public HttpApiV2AuthorizerMap deserialize(JsonParser jsonParser, Deserialization
}
if (node.has(LAMBDA_KEY)) {
Map context = LambdaContainerHandler.getObjectMapper().treeToValue(node.get(LAMBDA_KEY),
- TypeFactory.defaultInstance().constructMapType(HashMap.class, String.class, Object.class));
+ LambdaContainerHandler.getObjectMapper().getTypeFactory().constructMapType(HashMap.class, String.class, Object.class));
map.put(LAMBDA_KEY, context);
}
if (node.has(IAM_KEY)) {
@@ -110,16 +108,19 @@ public HttpApiV2AuthorizerSerializer() {
@Override
public void serialize(HttpApiV2AuthorizerMap httpApiV2AuthorizerMap, JsonGenerator jsonGenerator,
- SerializerProvider serializerProvider) throws IOException {
+ SerializationContext serializationContext) {
jsonGenerator.writeStartObject();
if (httpApiV2AuthorizerMap.isJwt()) {
- jsonGenerator.writeObjectField(JWT_KEY, httpApiV2AuthorizerMap.getJwtAuthorizer());
+ jsonGenerator.writeName(JWT_KEY);
+ jsonGenerator.writePOJO(httpApiV2AuthorizerMap.getJwtAuthorizer());
}
if (httpApiV2AuthorizerMap.isLambda()) {
- jsonGenerator.writeObjectField(LAMBDA_KEY, httpApiV2AuthorizerMap.getLambdaAuthorizerContext());
+ jsonGenerator.writeName(LAMBDA_KEY);
+ jsonGenerator.writePOJO(httpApiV2AuthorizerMap.getLambdaAuthorizerContext());
}
if (httpApiV2AuthorizerMap.isIam()) {
- jsonGenerator.writeObjectField(IAM_KEY, httpApiV2AuthorizerMap.get(IAM_KEY));
+ jsonGenerator.writeName(IAM_KEY);
+ jsonGenerator.writePOJO(httpApiV2AuthorizerMap.get(IAM_KEY));
}
jsonGenerator.writeEndObject();
}
diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java
index 012827e40..e15b9876f 100644
--- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java
+++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java
@@ -5,8 +5,8 @@
import com.amazonaws.serverless.exceptions.InvalidResponseObjectException;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.model.ErrorModel;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
@@ -47,7 +47,7 @@ void typedHandle_InvalidRequestEventException_500State() {
@Test
void typedHandle_InvalidRequestEventException_responseString()
- throws JsonProcessingException {
+ throws JacksonException {
AwsProxyResponse resp = exceptionHandler.handle(new InvalidRequestEventException(INVALID_REQUEST_MESSAGE, null));
assertNotNull(resp);
@@ -74,7 +74,7 @@ void typedHandle_InvalidResponseObjectException_502State() {
@Test
void typedHandle_InvalidResponseObjectException_responseString()
- throws JsonProcessingException {
+ throws JacksonException {
AwsProxyResponse resp = exceptionHandler.handle(new InvalidResponseObjectException(INVALID_RESPONSE_MESSAGE, null));
assertNotNull(resp);
@@ -106,7 +106,7 @@ void typedHandle_InternalServerErrorException_500State() {
@Test
void typedHandle_InternalServerErrorException_responseString()
- throws JsonProcessingException {
+ throws JacksonException {
InternalServerErrorException mockInternalServerErrorException = Mockito.mock(InternalServerErrorException.class);
Mockito.when(mockInternalServerErrorException.getMessage()).thenReturn(INTERNAL_SERVER_ERROR_MESSAGE);
@@ -131,7 +131,7 @@ void typedHandle_InternalServerErrorException_jsonContentTypeHeader() {
@Test
void typedHandle_NullPointerException_responseObject()
- throws JsonProcessingException {
+ throws JacksonException {
AwsProxyResponse resp = exceptionHandler.handle(new NullPointerException());
assertNotNull(resp);
@@ -248,7 +248,7 @@ void getErrorJson_ErrorModel_validJson()
void getErrorJson_JsonParsinException_validJson()
throws IOException {
ObjectMapper mockMapper = mock(ObjectMapper.class);
- JsonProcessingException exception = mock(JsonProcessingException.class);
+ JacksonException exception = mock(JacksonException.class);
when(mockMapper.writeValueAsString(any(Object.class))).thenThrow(exception);
String output = exceptionHandler.getErrorJson(INVALID_RESPONSE_MESSAGE);
diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java
index e42130453..a817bd2bf 100644
--- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java
+++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java
@@ -15,8 +15,8 @@
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
import com.amazonaws.serverless.proxy.model.*;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.io.IOUtils;
import org.apache.hc.core5.http.ContentType;
@@ -106,7 +106,7 @@ public AwsProxyRequestBuilder alb() {
try {
String json = objectMapper.writeValueAsString(this.request);
albRequest = objectMapper.readValue(json, AwsProxyRequest.class);
- } catch (JsonProcessingException jpe) {
+ } catch (JacksonException jpe) {
throw new RuntimeException(jpe);
}
@@ -265,7 +265,7 @@ public AwsProxyRequestBuilder body(Object body) {
if (request.getMultiValueHeaders() != null && request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON)) {
try {
return body(LambdaContainerHandler.getObjectMapper().writeValueAsString(body));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
throw new UnsupportedOperationException("Could not serialize object: " + e.getMessage());
}
} else {
@@ -438,7 +438,7 @@ public InputStream buildStream() {
try {
String requestJson = LambdaContainerHandler.getObjectMapper().writeValueAsString(request);
return new ByteArrayInputStream(requestJson.getBytes(StandardCharsets.UTF_8));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
return null;
}
}
@@ -448,7 +448,7 @@ public InputStream toHttpApiV2RequestStream() {
try {
String requestJson = LambdaContainerHandler.getObjectMapper().writeValueAsString(req);
return new ByteArrayInputStream(requestJson.getBytes(StandardCharsets.UTF_8));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
return null;
}
}
diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java
index 6d0aa9eeb..07bdff979 100644
--- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java
+++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java
@@ -7,7 +7,7 @@
import java.io.IOException;
import org.junit.jupiter.api.Test;
import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ObjectMapper;
public class AwsProxyRequestTest {
private static final String CUSTOM_HEADER_KEY_LOWER_CASE = "custom-header";
diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java
index 20ff4dff2..e51a1602b 100644
--- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java
+++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/HttpApiV2ProxyRequestTest.java
@@ -1,7 +1,7 @@
package com.amazonaws.serverless.proxy.model;
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import tools.jackson.core.JacksonException;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
@@ -181,7 +181,7 @@ void deserialize_fromJsonString_authorizerPopulatedCorrectly() {
assertTrue(req.getRequestContext().getAuthorizer().getJwtAuthorizer().getClaims().containsKey("claim1"));
assertEquals(2, req.getRequestContext().getAuthorizer().getJwtAuthorizer().getScopes().size());
assertEquals(RequestSource.API_GATEWAY, req.getRequestSource());
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing request" + e.getMessage());
}
@@ -196,7 +196,7 @@ void deserialize_fromJsonString_authorizerEmptyMap() {
assertFalse(req.getRequestContext().getAuthorizer().isJwt());
assertFalse(req.getRequestContext().getAuthorizer().isLambda());
assertFalse(req.getRequestContext().getAuthorizer().isIam());
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing request" + e.getMessage());
}
@@ -212,7 +212,7 @@ void deserialize_fromJsonString_lambdaAuthorizer() {
assertTrue(req.getRequestContext().getAuthorizer().isLambda());
assertEquals(5, req.getRequestContext().getAuthorizer().getLambdaAuthorizerContext().size());
assertEquals(1, req.getRequestContext().getAuthorizer().getLambdaAuthorizerContext().get("numberKey"));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing request" + e.getMessage());
}
@@ -239,7 +239,7 @@ void deserialize_fromJsonString_iamAuthorizer() {
req.getRequestContext().getAuthorizer().getIamAuthorizer().getUserArn());
assertEquals("AIDACOSFODNN7EXAMPLE2",
req.getRequestContext().getAuthorizer().getIamAuthorizer().getUserId());
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing request" + e.getMessage());
}
@@ -254,7 +254,7 @@ void deserialize_fromJsonString_isBase64EncodedPopulates() {
req = LambdaContainerHandler.getObjectMapper().readValue(NO_AUTH_PROXY, HttpApiV2ProxyRequest.class);
assertTrue(req.isBase64Encoded());
assertEquals(RequestSource.API_GATEWAY, req.getRequestSource());
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing request" + e.getMessage());
}
@@ -277,7 +277,7 @@ void serialize_toJsonString_authorizerPopulatesCorrectly() {
assertTrue(reqString.contains("\"scopes\":[\"first\",\"second\"]"));
assertTrue(reqString.contains("\"authorizer\":{\"jwt\":{"));
assertTrue(reqString.contains("\"isBase64Encoded\":false"));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while serializing request" + e.getMessage());
}
diff --git a/aws-serverless-java-container-jersey/pom.xml b/aws-serverless-java-container-jersey/pom.xml
index 1788a36b8..8fcc07c4b 100644
--- a/aws-serverless-java-container-jersey/pom.xml
+++ b/aws-serverless-java-container-jersey/pom.xml
@@ -6,12 +6,12 @@
AWS Serverless Java container support - Jersey implementation
Allows Java applications written for Jersey to run in AWS Lambda
https://aws.amazon.com/lambda
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
..
@@ -24,18 +24,12 @@
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
-
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
tests
test-jar
test
@@ -68,14 +62,6 @@
test
-
- com.fasterxml.jackson.core
- jackson-databind
- ${jackson.version}
- true
- test
-
-
org.glassfish.jersey.media
jersey-media-json-jackson
@@ -88,11 +74,11 @@
jackson-annotations
- com.fasterxml.jackson.core
+ tools.jackson.core
jackson-databind
- com.fasterxml.jackson.core
+ tools.jackson.core
jackson-core
diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java
index b845a0b69..23f1078fa 100644
--- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java
+++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java
@@ -24,8 +24,8 @@
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;
import com.amazonaws.services.lambda.runtime.Context;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Base64;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
@@ -303,7 +303,7 @@ void error_statusCode_methodNotAllowed(String reqType) {
@MethodSource("data")
@ParameterizedTest
- void responseBody_responseWriter_validBody(String reqType) throws JsonProcessingException {
+ void responseBody_responseWriter_validBody(String reqType) throws JacksonException {
initJerseyAwsProxyTest(reqType);
SingleValueModel singleValueModel = new SingleValueModel();
singleValueModel.setValue(CUSTOM_HEADER_VALUE);
@@ -460,7 +460,7 @@ private void validateMapResponseModel(AwsProxyResponse output, String key, Strin
MapResponseModel response = objectMapper.readValue(output.getBody(), MapResponseModel.class);
assertNotNull(response.getValues().get(key));
assertEquals(value, response.getValues().get(key));
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
@@ -471,7 +471,7 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) {
SingleValueModel response = objectMapper.readValue(output.getBody(), SingleValueModel.class);
assertNotNull(response.getValue());
assertEquals(value, response.getValue());
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java
index 9dc1ab32a..5ca09b11b 100644
--- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java
+++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java
@@ -11,7 +11,8 @@
import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;
import com.amazonaws.services.lambda.runtime.Context;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.core.JacksonException;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.jupiter.api.Disabled;
@@ -281,7 +282,7 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) {
SingleValueModel response = objectMapper.readValue(output.getBody(), SingleValueModel.class);
assertNotNull(response.getValue());
assertEquals(value, response.getValue());
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
@@ -292,7 +293,7 @@ private void validateMapResponseModel(AwsProxyResponse output, String key, Strin
MapResponseModel response = objectMapper.readValue(output.getBody(), MapResponseModel.class);
assertNotNull(response.getValues().get(key));
assertEquals(value, response.getValues().get(key));
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml
index f03d9db0a..d85534faf 100644
--- a/aws-serverless-java-container-spring/pom.xml
+++ b/aws-serverless-java-container-spring/pom.xml
@@ -6,18 +6,18 @@
AWS Serverless Java container support - Spring implementation
Allows Java applications written for the Spring framework to run in AWS Lambda
https://aws.amazon.com/lambda
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
..
- 6.2.8
- 6.5.1
+ 7.0.0
+ 7.0.0
@@ -25,12 +25,12 @@
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
+ 3.0.0-SNAPSHOT
tests
test-jar
test
@@ -57,12 +57,7 @@
test
-
- com.fasterxml.jackson.core
- jackson-databind
- ${jackson.version}
- test
-
+
jakarta.activation
diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java
index 9504b6166..4b7d3eb81 100644
--- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java
+++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java
@@ -15,8 +15,8 @@
import com.amazonaws.serverless.proxy.spring.echoapp.model.MapResponseModel;
import com.amazonaws.serverless.proxy.spring.echoapp.model.SingleValueModel;
import com.amazonaws.services.lambda.runtime.Context;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Base64;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
@@ -294,7 +294,7 @@ void error_unauthenticatedCall_filterStepsRequest(String reqType) {
@MethodSource("data")
@ParameterizedTest
- void responseBody_responseWriter_validBody(String reqType) throws JsonProcessingException {
+ void responseBody_responseWriter_validBody(String reqType) throws JacksonException {
initSpringAwsProxyTest(reqType);
SingleValueModel singleValueModel = new SingleValueModel();
singleValueModel.setValue(CUSTOM_HEADER_VALUE);
@@ -311,7 +311,7 @@ void responseBody_responseWriter_validBody(String reqType) throws JsonProcessing
@MethodSource("data")
@ParameterizedTest
- void responseBody_responseWriter_validBody_UTF(String reqType) throws JsonProcessingException {
+ void responseBody_responseWriter_validBody_UTF(String reqType) throws JacksonException {
initSpringAwsProxyTest(reqType);
SingleValueModel singleValueModel = new SingleValueModel();
singleValueModel.setValue(UNICODE_VALUE);
@@ -363,7 +363,7 @@ void injectBody_populatedResponse_noException(String reqType) {
try {
SingleValueModel output = objectMapper.readValue(response.getBody(), SingleValueModel.class);
assertEquals("true", output.getValue());
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail();
}
@@ -373,7 +373,7 @@ void injectBody_populatedResponse_noException(String reqType) {
try {
SingleValueModel output = objectMapper.readValue(emptyResp.getBody(), SingleValueModel.class);
assertNull(output.getValue());
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail();
}
@@ -392,7 +392,7 @@ void servletRequestEncoding_acceptEncoding_okStatusCode(String reqType) {
.header(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate")
.queryString("status", "200")
.body(objectMapper.writeValueAsString(singleValueModel));
- } catch (JsonProcessingException e) {
+ } catch (JacksonException e) {
fail("Could not serialize object to JSON");
}
@@ -484,7 +484,7 @@ private void validateMapResponseModel(AwsProxyResponse output) {
MapResponseModel response = objectMapper.readValue(output.getBody(), MapResponseModel.class);
assertNotNull(response.getValues().get(CUSTOM_HEADER_KEY));
assertEquals(CUSTOM_HEADER_VALUE, response.getValues().get(CUSTOM_HEADER_KEY));
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
@@ -495,7 +495,7 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) {
SingleValueModel response = objectMapper.readValue(output.getBody(), SingleValueModel.class);
assertNotNull(response.getValue());
assertEquals(value, response.getValue());
- } catch (IOException e) {
+ } catch (JacksonException e) {
e.printStackTrace();
fail("Exception while parsing response body: " + e.getMessage());
}
diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java
index cf4aa495e..9b3e0cecc 100644
--- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java
+++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java
@@ -1,7 +1,7 @@
package com.amazonaws.serverless.proxy.spring.echoapp;
import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java
index 7742db7f6..d08a3e45c 100644
--- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java
+++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/profile/SpringProfileTest.java
@@ -8,7 +8,7 @@
import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler;
import com.amazonaws.serverless.proxy.spring.echoapp.EchoSpringAppConfig;
import com.amazonaws.serverless.proxy.spring.echoapp.model.MapResponseModel;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
diff --git a/aws-serverless-java-container-springboot3/pom.xml b/aws-serverless-java-container-springboot3/pom.xml
index 5dcac1562..3ababeb9b 100644
--- a/aws-serverless-java-container-springboot3/pom.xml
+++ b/aws-serverless-java-container-springboot3/pom.xml
@@ -3,7 +3,8 @@
aws-serverless-java-container
com.amazonaws.serverless
- 2.1.5-SNAPSHOT
+ 2.1.3
+ ..
4.0.0
@@ -12,11 +13,10 @@
AWS Serverless Java container support - SpringBoot 3 implementation
Allows Java applications written for SpringBoot 3 to run in AWS Lambda
https://aws.amazon.com/lambda
- 2.1.5-SNAPSHOT
6.2.8
- 3.4.5
+ 3.5.8
6.4.5
@@ -30,16 +30,22 @@
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
+ 2.1.3
com.amazonaws.serverless
aws-serverless-java-container-core
- 2.1.5-SNAPSHOT
+ 2.1.3
tests
test-jar
test
+
+ com.github.spotbugs
+ spotbugs-annotations
+ 4.9.3
+ provided
+
org.springframework
spring-webflux
@@ -296,6 +302,9 @@
com.github.spotbugs
spotbugs-maven-plugin
+
+ ${project.basedir}/spotbugs-excludeFilter.xml
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aws-serverless-java-container-springboot4/pom.xml b/aws-serverless-java-container-springboot4/pom.xml
new file mode 100644
index 000000000..e12ac19b5
--- /dev/null
+++ b/aws-serverless-java-container-springboot4/pom.xml
@@ -0,0 +1,408 @@
+
+
+
+ aws-serverless-java-container
+ com.amazonaws.serverless
+ 3.0.0-SNAPSHOT
+ ..
+
+ 4.0.0
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-springboot4
+ AWS Serverless Java container support - SpringBoot 4 implementation
+ Allows Java applications written for SpringBoot 4 to run in AWS Lambda
+ https://aws.amazon.com/lambda
+
+
+ 7.0.1
+ 4.0.0
+ 7.0.0
+
+
+
+
+
+ org.springframework
+ spring-framework-bom
+ ${spring.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-function-serverless-web
+ 5.0.0
+
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-core
+ 3.0.0-SNAPSHOT
+
+
+ com.amazonaws.serverless
+ aws-serverless-java-container-core
+ 3.0.0-SNAPSHOT
+ tests
+ test-jar
+ test
+
+
+ com.github.spotbugs
+ spotbugs-annotations
+ 4.9.3
+ provided
+
+
+ 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.boot
+ spring-boot-web-server
+ ${springboot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${springboot.version}
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+ org.springframework.boot
+ spring-boot-webmvc
+ ${springboot.version}
+
+
+ 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
+ 9.1.0.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-webmvc
+
+
+ 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.springframework.boot
+ spring-boot-starter-webflux
+ ${springboot.version}
+ true
+
+
+ org.glassfish.expressly
+ expressly
+ 6.0.0
+ 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
+
+ ${project.basedir}/spotbugs-excludeFilter.xml
+
+
+
+
+ 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-springboot4/spotbugs-excludeFilter.xml b/aws-serverless-java-container-springboot4/spotbugs-excludeFilter.xml
new file mode 100644
index 000000000..b26b1ad24
--- /dev/null
+++ b/aws-serverless-java-container-springboot4/spotbugs-excludeFilter.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringAotTypesProcessor.java b/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringAotTypesProcessor.java
new file mode 100644
index 000000000..0a461aa97
--- /dev/null
+++ b/aws-serverless-java-container-springboot4/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 tools.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-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java b/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java
new file mode 100644
index 000000000..15984bc4e
--- /dev/null
+++ b/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java
@@ -0,0 +1,224 @@
+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.internal.servlet.AwsProxyHttpServletRequest;
+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 tools.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);
+
+ // Use AWS container's servlet request instead of Spring Cloud Function's
+ AwsProxyHttpServletRequest httpServletRequest = new AwsProxyHttpServletRequest(v1Request, lambdaContext, securityWriter.writeSecurityContext(v1Request, lambdaContext));
+ httpServletRequest.setServletContext(servletContext);
+ return httpServletRequest;
+ }
+
+
+
+ @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-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebCustomRuntimeEventLoop.java b/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebCustomRuntimeEventLoop.java
new file mode 100644
index 000000000..970418246
--- /dev/null
+++ b/aws-serverless-java-container-springboot4/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringWebCustomRuntimeEventLoop.java
@@ -0,0 +1,187 @@
+/*
+ * 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.server.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 jakarta.servlet.http.HttpServletRequest;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.SerializationFeature;
+import tools.jackson.databind.json.JsonMapper;
+
+/**
+ * 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 = JsonMapper.builder()
+ .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
+ .build();
+ 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");
+
+ HttpServletRequest httpServletRequest = AwsSpringHttpProcessingUtils.generateHttpServletRequest(
+ incomingEvent.getBody(), null, mvc.getServletContext(), mapper);
+ httpServletRequest.startAsync();
+ AwsProxyResponse awsResponse = AwsSpringHttpProcessingUtils.processRequest(
+ httpServletRequest, 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