diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml
index 8fbdeebc6..d561495bb 100644
--- a/aws-serverless-java-container-core/pom.xml
+++ b/aws-serverless-java-container-core/pom.xml
@@ -18,7 +18,6 @@
3.1.0
6.1.0
- 3.0.2
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java
index 7437449e1..fa789d590 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java
@@ -153,8 +153,14 @@ protected void doFilter(HttpServletRequest request, HttpServletResponse response
FilterChain chain = getFilterChain(request, servlet);
chain.doFilter(request, response);
if(requiresAsyncReDispatch(request)) {
+ AsyncContext dispatchedContext = request.getAsyncContext();
chain = getFilterChain(request, servlet);
chain.doFilter(request, response);
+ // like a servlet container, we finish the response after the async dispatch unless the dispatch
+ // target started a new async cycle. Spring 7.0.6+ no longer flushes the body stream for us.
+ if (!response.isCommitted() && !startedNewAsyncContext(request, dispatchedContext)) {
+ response.flushBuffer();
+ }
}
// if for some reason the response wasn't flushed yet, we force it here unless it's being processed asynchronously (WebFlux)
if (!response.isCommitted() && request.getDispatcherType() != DispatcherType.ASYNC) {
@@ -162,6 +168,10 @@ protected void doFilter(HttpServletRequest request, HttpServletResponse response
}
}
+ private boolean startedNewAsyncContext(HttpServletRequest request, AsyncContext dispatchedContext) {
+ return request.isAsyncStarted() && request.getAsyncContext() != dispatchedContext;
+ }
+
private boolean requiresAsyncReDispatch(HttpServletRequest request) {
if (request.isAsyncStarted()) {
AsyncContext asyncContext = request.getAsyncContext();
diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java
index a8383b5c3..c65462b5e 100644
--- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java
+++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java
@@ -14,16 +14,21 @@
import org.junit.jupiter.api.Test;
import jakarta.servlet.AsyncContext;
+import jakarta.servlet.DispatcherType;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRegistration;
+import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
public class AwsAsyncContextTest {
private MockLambdaContext lambdaCtx = new MockLambdaContext();
@@ -46,6 +51,47 @@ void dispatch_amendsPath() throws InvalidRequestEventException {
assertEquals("/srv1/hello", req.getRequestURI());
}
+ @Test
+ void doFilter_asyncDispatchWritesWithoutFlush_flushesResponse() throws Exception {
+ PassThroughContainerHandler passThroughHandler = new PassThroughContainerHandler();
+ AsyncDispatchServlet servlet = new AsyncDispatchServlet(false);
+ AwsProxyHttpServletRequest req = asyncDispatchRequest(passThroughHandler, servlet);
+ CountDownLatch latch = new CountDownLatch(1);
+ AwsHttpServletResponse resp = new AwsHttpServletResponse(req, latch);
+ req.setResponse(resp);
+
+ passThroughHandler.doFilter(req, resp, servlet);
+
+ assertTrue(resp.isCommitted());
+ assertEquals(0, latch.getCount());
+ assertEquals("hello", resp.getAwsResponseBodyString());
+ }
+
+ @Test
+ void doFilter_asyncDispatchStartsNewAsync_leavesResponseOpen() throws Exception {
+ PassThroughContainerHandler passThroughHandler = new PassThroughContainerHandler();
+ AsyncDispatchServlet servlet = new AsyncDispatchServlet(true);
+ AwsProxyHttpServletRequest req = asyncDispatchRequest(passThroughHandler, servlet);
+ CountDownLatch latch = new CountDownLatch(1);
+ AwsHttpServletResponse resp = new AwsHttpServletResponse(req, latch);
+ req.setResponse(resp);
+
+ passThroughHandler.doFilter(req, resp, servlet);
+
+ assertFalse(resp.isCommitted());
+ assertEquals(1, latch.getCount());
+ }
+
+ private AwsProxyHttpServletRequest asyncDispatchRequest(PassThroughContainerHandler passThroughHandler, Servlet servlet) throws InvalidRequestEventException {
+ AwsServletContext servletCtx = new AwsServletContext(passThroughHandler);
+ passThroughHandler.setServletContext(servletCtx);
+ servletCtx.addServlet("async", servlet).addMapping("/async");
+
+ AwsProxyHttpServletRequest req = (AwsProxyHttpServletRequest)reader.readRequest(new AwsProxyRequestBuilder("/async", "GET").build(), null, lambdaCtx, LambdaContainerHandler.getContainerConfig());
+ req.setServletContext(servletCtx);
+ return req;
+ }
+
private AwsServletContext getCtx() {
AwsServletContext ctx = new AwsServletContext(handler);
@@ -59,6 +105,54 @@ private AwsServletContext getCtx() {
return ctx;
}
+ /**
+ * Behaves like Spring MVC 7.0.6+ with a result that's ready right away: starts async and dispatches during the
+ * first pass, then on the async dispatch writes the body without flushing it.
+ */
+ private static class AsyncDispatchServlet extends HttpServlet {
+ private final boolean startAsyncOnDispatch;
+
+ AsyncDispatchServlet(boolean startAsyncOnDispatch) {
+ this.startAsyncOnDispatch = startAsyncOnDispatch;
+ }
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ if (req.getDispatcherType() != DispatcherType.ASYNC) {
+ req.startAsync().dispatch();
+ } else if (startAsyncOnDispatch) {
+ req.startAsync();
+ } else {
+ resp.setStatus(200);
+ resp.getOutputStream().write("hello".getBytes(StandardCharsets.UTF_8));
+ }
+ }
+ }
+
+ /**
+ * Uses the real doFilter from AwsLambdaServletContainerHandler, unlike MockContainerHandler.
+ */
+ public static class PassThroughContainerHandler extends AwsLambdaServletContainerHandler {
+ public PassThroughContainerHandler() {
+ super(AwsProxyRequest.class, AwsProxyResponse.class, new AwsProxyHttpServletRequestReader(), new AwsProxyHttpServletResponseWriter(), new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler());
+ }
+
+ @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 {
+
+ }
+
+ @Override
+ public void initialize() throws ContainerInitializationException {
+
+ }
+ }
+
public static class MockContainerHandler extends AwsLambdaServletContainerHandler {
private int desiredStatus;
private HttpServletResponse response;
diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml
index 1cafa8660..761923ef0 100644
--- a/aws-serverless-java-container-spring/pom.xml
+++ b/aws-serverless-java-container-spring/pom.xml
@@ -16,8 +16,8 @@
- 7.0.0
- 7.0.0
+ 7.0.9
+ 7.0.7
diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/AsyncAppTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/AsyncAppTest.java
index 8265e1bf4..09cd88b17 100644
--- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/AsyncAppTest.java
+++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/AsyncAppTest.java
@@ -9,6 +9,7 @@
import com.amazonaws.serverless.proxy.spring.springapp.MessageController;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
@@ -36,6 +37,7 @@ void springApp_helloRequest_returnsCorrect() {
}
@Test
+ @Timeout(30)
void springApp_asyncRequest_returnsCorrect() {
AwsProxyRequest req = new AwsProxyRequestBuilder("/async", "GET").build();
AwsProxyResponse resp = handler.handleRequest(req, new MockLambdaContext());
diff --git a/aws-serverless-java-container-springboot4/pom.xml b/aws-serverless-java-container-springboot4/pom.xml
index 9c7d90921..4e818d7d1 100644
--- a/aws-serverless-java-container-springboot4/pom.xml
+++ b/aws-serverless-java-container-springboot4/pom.xml
@@ -15,13 +15,21 @@
https://aws.amazon.com/lambda
- 7.0.1
- 4.0.0
- 7.0.0
+ 7.0.9
+ 4.0.8
+ 7.0.7
+
+
+ tools.jackson
+ jackson-bom
+ ${jackson.version}
+ pom
+ import
+
org.springframework
spring-framework-bom
@@ -29,6 +37,17 @@
pom
import
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ org.jspecify
+ jspecify
+ 1.0.1
+
@@ -37,7 +56,7 @@
org.springframework.cloud
spring-cloud-function-serverless-web
- 5.0.0
+ 5.0.4
com.amazonaws.serverless
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 9ba701351..51bdc7024 100644
--- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/build.gradle
+++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/build.gradle
@@ -7,8 +7,8 @@ repositories {
dependencies {
implementation (
- 'org.springframework:spring-webmvc:6.2.8',
- 'org.springframework:spring-context:6.2.8',
+ 'org.springframework:spring-webmvc:7.0.9',
+ 'org.springframework:spring-context:7.0.9',
'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',
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 3a03375c8..c49d347cd 100644
--- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/pom.xml
@@ -16,7 +16,7 @@
1.8
1.8
- 7.0.0
+ 7.0.9
6.0.0
2.24.2
diff --git a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/pom.xml
index c122a405a..f44d5280b 100644
--- a/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/aws-serverless-springboot4-archetype/src/main/resources/archetype-resources/pom.xml
@@ -16,12 +16,16 @@
org.springframework.boot
spring-boot-starter-parent
- 4.0.0
+ 4.0.8
17
5.12.1
+
+ 3.2.4
+ 2.22.4
diff --git a/pom.xml b/pom.xml
index 50257e2e3..1b8c4b626 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,8 +78,8 @@
0.7
12.1.1
-
- 2.0.17
+ 3.2.4
+ 2.0.18
6.0.0
5.19.0
1.3