Skip to content

Commit ff412de

Browse files
committed
Use wrapped response in HandlerFunctionAdapter
webmvc.fn now also uses the StandardServletAsyncWebRequest wrapped response to enforce lifecycle rules from Servlet spec (section 2.3.3.4). See gh-32342
1 parent 5d572f6 commit ff412de

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-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-2021 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.
@@ -94,6 +94,7 @@ public ModelAndView handle(HttpServletRequest servletRequest,
9494
Object handler) throws Exception {
9595

9696
WebAsyncManager asyncManager = getWebAsyncManager(servletRequest, servletResponse);
97+
servletResponse = getWrappedResponse(asyncManager);
9798

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

127+
/**
128+
* Obtain response wrapped by
129+
* {@link org.springframework.web.context.request.async.StandardServletAsyncWebRequest}
130+
* to enforce lifecycle rules from Servlet spec (section 2.3.3.4)
131+
* in case of async handling.
132+
*/
133+
private static HttpServletResponse getWrappedResponse(WebAsyncManager asyncManager) {
134+
AsyncWebRequest asyncRequest = asyncManager.getAsyncWebRequest();
135+
Assert.notNull(asyncRequest, "No AsyncWebRequest");
136+
137+
HttpServletResponse servletResponse = asyncRequest.getNativeResponse(HttpServletResponse.class);
138+
Assert.notNull(servletResponse, "No HttpServletResponse");
139+
140+
return servletResponse;
141+
}
142+
126143
private ServerRequest getServerRequest(HttpServletRequest servletRequest) {
127144
ServerRequest serverRequest =
128145
(ServerRequest) servletRequest.getAttribute(RouterFunctions.REQUEST_ATTRIBUTE);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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.Collections;
21+
22+
import javax.servlet.AsyncEvent;
23+
import javax.servlet.http.HttpServletResponse;
24+
25+
import org.junit.jupiter.api.BeforeEach;
26+
import org.junit.jupiter.api.Test;
27+
28+
import org.springframework.http.converter.StringHttpMessageConverter;
29+
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
30+
import org.springframework.web.context.request.async.StandardServletAsyncWebRequest;
31+
import org.springframework.web.context.request.async.WebAsyncManager;
32+
import org.springframework.web.context.request.async.WebAsyncUtils;
33+
import org.springframework.web.servlet.function.HandlerFunction;
34+
import org.springframework.web.servlet.function.RouterFunctions;
35+
import org.springframework.web.servlet.function.ServerRequest;
36+
import org.springframework.web.servlet.function.ServerResponse;
37+
import org.springframework.web.testfixture.servlet.MockAsyncContext;
38+
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
39+
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
40+
41+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
42+
import static org.mockito.BDDMockito.doThrow;
43+
import static org.mockito.BDDMockito.mock;
44+
45+
/**
46+
* Unit tests for {@link HandlerFunctionAdapter}.
47+
*
48+
* @author Rossen Stoyanchev
49+
*/
50+
public class HandlerFunctionAdapterTests {
51+
52+
private final MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
53+
54+
private final MockHttpServletResponse servletResponse = new MockHttpServletResponse();
55+
56+
private final HandlerFunctionAdapter adapter = new HandlerFunctionAdapter();
57+
58+
59+
@BeforeEach
60+
void setUp() {
61+
this.servletRequest.setAttribute(RouterFunctions.REQUEST_ATTRIBUTE,
62+
ServerRequest.create(this.servletRequest, Collections.singletonList(new StringHttpMessageConverter())));
63+
}
64+
65+
66+
@Test
67+
void asyncRequestNotUsable() throws Exception {
68+
69+
HandlerFunction<?> handler = request -> ServerResponse.sse(sseBuilder -> {
70+
try {
71+
sseBuilder.data("data 1");
72+
sseBuilder.data("data 2");
73+
}
74+
catch (IOException ex) {
75+
throw new RuntimeException(ex);
76+
}
77+
});
78+
79+
this.servletRequest.setAsyncSupported(true);
80+
81+
HttpServletResponse mockServletResponse = mock(HttpServletResponse.class);
82+
doThrow(new IOException("Broken pipe")).when(mockServletResponse).getOutputStream();
83+
84+
// Use of response should be rejected
85+
assertThatThrownBy(() -> adapter.handle(servletRequest, mockServletResponse, handler))
86+
.hasRootCauseInstanceOf(IOException.class)
87+
.hasRootCauseMessage("Broken pipe");
88+
}
89+
90+
@Test
91+
void asyncRequestNotUsableOnAsyncDispatch() throws Exception {
92+
93+
HandlerFunction<?> handler = request -> ServerResponse.ok().body("body");
94+
95+
// Put AsyncWebRequest in ERROR state
96+
StandardServletAsyncWebRequest asyncRequest = new StandardServletAsyncWebRequest(servletRequest, servletResponse);
97+
asyncRequest.onError(new AsyncEvent(new MockAsyncContext(servletRequest, servletResponse), new Exception()));
98+
99+
// Set it as the current AsyncWebRequest, from the initial REQUEST dispatch
100+
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(servletRequest);
101+
asyncManager.setAsyncWebRequest(asyncRequest);
102+
103+
// Use of response should be rejected
104+
assertThatThrownBy(() -> adapter.handle(servletRequest, servletResponse, handler))
105+
.isInstanceOf(AsyncRequestNotUsableException.class);
106+
}
107+
108+
}

0 commit comments

Comments
 (0)