Skip to content

Commit 1ffffef

Browse files
committed
Merge branch '6.1.x'
2 parents f003983 + dec5265 commit 1ffffef

File tree

2 files changed

+125
-1
lines changed

2 files changed

+125
-1
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/HandlerFunctionAdapter.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -95,6 +95,7 @@ public ModelAndView handle(HttpServletRequest servletRequest,
9595
Object handler) throws Exception {
9696

9797
WebAsyncManager asyncManager = getWebAsyncManager(servletRequest, servletResponse);
98+
servletResponse = getWrappedResponse(asyncManager);
9899

99100
ServerRequest serverRequest = getServerRequest(servletRequest);
100101
ServerResponse serverResponse;
@@ -124,6 +125,22 @@ private WebAsyncManager getWebAsyncManager(HttpServletRequest servletRequest, Ht
124125
return asyncManager;
125126
}
126127

128+
/**
129+
* Obtain response wrapped by
130+
* {@link org.springframework.web.context.request.async.StandardServletAsyncWebRequest}
131+
* to enforce lifecycle rules from Servlet spec (section 2.3.3.4)
132+
* in case of async handling.
133+
*/
134+
private static HttpServletResponse getWrappedResponse(WebAsyncManager asyncManager) {
135+
AsyncWebRequest asyncRequest = asyncManager.getAsyncWebRequest();
136+
Assert.notNull(asyncRequest, "No AsyncWebRequest");
137+
138+
HttpServletResponse servletResponse = asyncRequest.getNativeResponse(HttpServletResponse.class);
139+
Assert.notNull(servletResponse, "No HttpServletResponse");
140+
141+
return servletResponse;
142+
}
143+
127144
private ServerRequest getServerRequest(HttpServletRequest servletRequest) {
128145
ServerRequest serverRequest =
129146
(ServerRequest) servletRequest.getAttribute(RouterFunctions.REQUEST_ATTRIBUTE);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.servlet.function.support;
18+
19+
import java.io.IOException;
20+
import java.util.List;
21+
22+
import jakarta.servlet.AsyncEvent;
23+
import jakarta.servlet.http.HttpServletResponse;
24+
import org.junit.jupiter.api.BeforeEach;
25+
import org.junit.jupiter.api.Test;
26+
27+
import org.springframework.http.converter.StringHttpMessageConverter;
28+
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
29+
import org.springframework.web.context.request.async.StandardServletAsyncWebRequest;
30+
import org.springframework.web.context.request.async.WebAsyncManager;
31+
import org.springframework.web.context.request.async.WebAsyncUtils;
32+
import org.springframework.web.servlet.function.HandlerFunction;
33+
import org.springframework.web.servlet.function.RouterFunctions;
34+
import org.springframework.web.servlet.function.ServerRequest;
35+
import org.springframework.web.servlet.function.ServerResponse;
36+
import org.springframework.web.testfixture.servlet.MockAsyncContext;
37+
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
38+
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
39+
40+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
41+
import static org.mockito.BDDMockito.doThrow;
42+
import static org.mockito.BDDMockito.mock;
43+
44+
/**
45+
* Unit tests for {@link HandlerFunctionAdapter}.
46+
*
47+
* @author Rossen Stoyanchev
48+
*/
49+
public class HandlerFunctionAdapterTests {
50+
51+
private final MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
52+
53+
private final MockHttpServletResponse servletResponse = new MockHttpServletResponse();
54+
55+
private final HandlerFunctionAdapter adapter = new HandlerFunctionAdapter();
56+
57+
58+
@BeforeEach
59+
void setUp() {
60+
this.servletRequest.setAttribute(RouterFunctions.REQUEST_ATTRIBUTE,
61+
ServerRequest.create(this.servletRequest, List.of(new StringHttpMessageConverter())));
62+
}
63+
64+
65+
@Test
66+
void asyncRequestNotUsable() throws Exception {
67+
68+
HandlerFunction<?> handler = request -> ServerResponse.sse(sseBuilder -> {
69+
try {
70+
sseBuilder.data("data 1");
71+
sseBuilder.data("data 2");
72+
}
73+
catch (IOException ex) {
74+
throw new RuntimeException(ex);
75+
}
76+
});
77+
78+
this.servletRequest.setAsyncSupported(true);
79+
80+
HttpServletResponse mockServletResponse = mock(HttpServletResponse.class);
81+
doThrow(new IOException("Broken pipe")).when(mockServletResponse).getOutputStream();
82+
83+
// Use of response should be rejected
84+
assertThatThrownBy(() -> adapter.handle(servletRequest, mockServletResponse, handler))
85+
.hasRootCauseInstanceOf(IOException.class)
86+
.hasRootCauseMessage("Broken pipe");
87+
}
88+
89+
@Test
90+
void asyncRequestNotUsableOnAsyncDispatch() throws Exception {
91+
92+
HandlerFunction<?> handler = request -> ServerResponse.ok().body("body");
93+
94+
// Put AsyncWebRequest in ERROR state
95+
StandardServletAsyncWebRequest asyncRequest = new StandardServletAsyncWebRequest(servletRequest, servletResponse);
96+
asyncRequest.onError(new AsyncEvent(new MockAsyncContext(servletRequest, servletResponse), new Exception()));
97+
98+
// Set it as the current AsyncWebRequest, from the initial REQUEST dispatch
99+
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(servletRequest);
100+
asyncManager.setAsyncWebRequest(asyncRequest);
101+
102+
// Use of response should be rejected
103+
assertThatThrownBy(() -> adapter.handle(servletRequest, servletResponse, handler))
104+
.isInstanceOf(AsyncRequestNotUsableException.class);
105+
}
106+
107+
}

0 commit comments

Comments
 (0)