Skip to content

Commit 47d9182

Browse files
committed
Merge branch '6.2.x'
2 parents 7ea619a + 9670388 commit 47d9182

File tree

4 files changed

+130
-7
lines changed

4 files changed

+130
-7
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -729,16 +729,21 @@ private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolver
729729
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
730730
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);
731731

732+
ResponseBodyEmitterReturnValueHandler responseBodyEmitterHandler =
733+
new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
734+
this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager,
735+
initViewResolvers(), initLocaleResolver());
736+
737+
HttpEntityMethodProcessor httpEntityMethodProcessor = new HttpEntityMethodProcessor(getMessageConverters(),
738+
this.contentNegotiationManager, this.requestResponseBodyAdvice, this.errorResponseInterceptors);
739+
732740
// Single-purpose return value types
733741
handlers.add(new ModelAndViewMethodReturnValueHandler());
734742
handlers.add(new ModelMethodProcessor());
735743
handlers.add(new ViewMethodReturnValueHandler());
736-
handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
737-
this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager,
738-
initViewResolvers(), initLocaleResolver()));
744+
handlers.add(responseBodyEmitterHandler);
739745
handlers.add(new StreamingResponseBodyReturnValueHandler());
740-
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
741-
this.contentNegotiationManager, this.requestResponseBodyAdvice, this.errorResponseInterceptors));
746+
handlers.add(new ResponseEntityReturnValueHandler(httpEntityMethodProcessor, responseBodyEmitterHandler));
742747
handlers.add(new HttpHeadersReturnValueHandler());
743748
handlers.add(new CallableMethodReturnValueHandler());
744749
handlers.add(new DeferredResultMethodReturnValueHandler());

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,11 @@ public boolean supportsReturnType(MethodParameter returnType) {
169169
ResolvableType.forMethodParameter(returnType).getGeneric().resolve() :
170170
returnType.getParameterType();
171171

172-
return (bodyType != null && (ResponseBodyEmitter.class.isAssignableFrom(bodyType) ||
173-
this.reactiveHandler.isReactiveType(bodyType)));
172+
return (bodyType != null && supportsBodyType(bodyType));
173+
}
174+
175+
boolean supportsBodyType(Class<?> bodyType) {
176+
return (ResponseBodyEmitter.class.isAssignableFrom(bodyType) || this.reactiveHandler.isReactiveType(bodyType));
174177
}
175178

176179
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2002-present 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.mvc.method.annotation;
18+
19+
import org.jspecify.annotations.Nullable;
20+
21+
import org.springframework.core.MethodParameter;
22+
import org.springframework.http.HttpEntity;
23+
import org.springframework.web.context.request.NativeWebRequest;
24+
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
25+
import org.springframework.web.method.support.ModelAndViewContainer;
26+
27+
/**
28+
* Handler for return values of type {@link org.springframework.http.ResponseEntity}
29+
* that delegates to one of the following:
30+
*
31+
* <ul>
32+
* <li>{@link HttpEntityMethodProcessor} for responses with a concrete body value
33+
* <li>{@link ResponseBodyEmitterReturnValueHandler} for responses with a body
34+
* that is a {@link ResponseBodyEmitter} or an async/reactive type.
35+
* </ul>
36+
*
37+
* <p>Use of this wrapper allows for late check in {@link #handleReturnValue} of
38+
* the type of the actual body value in case the method signature does not
39+
* provide enough information to decide via {@link #supportsReturnType}.
40+
*
41+
* @author Rossen Stoyanchev
42+
* @since 7.0
43+
*/
44+
public class ResponseEntityReturnValueHandler implements HandlerMethodReturnValueHandler {
45+
46+
private final HttpEntityMethodProcessor httpEntityMethodProcessor;
47+
48+
private final ResponseBodyEmitterReturnValueHandler responseBodyEmitterHandler;
49+
50+
51+
public ResponseEntityReturnValueHandler(
52+
HttpEntityMethodProcessor httpEntityMethodProcessor,
53+
ResponseBodyEmitterReturnValueHandler responseBodyEmitterHandler) {
54+
55+
this.httpEntityMethodProcessor = httpEntityMethodProcessor;
56+
this.responseBodyEmitterHandler = responseBodyEmitterHandler;
57+
}
58+
59+
60+
@Override
61+
public boolean supportsReturnType(MethodParameter returnType) {
62+
return this.httpEntityMethodProcessor.supportsReturnType(returnType);
63+
}
64+
65+
@Override
66+
public void handleReturnValue(
67+
@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
68+
NativeWebRequest request) throws Exception {
69+
70+
if (returnValue instanceof HttpEntity<?> httpEntity) {
71+
Object body = httpEntity.getBody();
72+
if (body != null) {
73+
if (this.responseBodyEmitterHandler.supportsBodyType(body.getClass())) {
74+
this.responseBodyEmitterHandler.handleReturnValue(returnValue, returnType, mavContainer, request);
75+
return;
76+
}
77+
}
78+
}
79+
80+
this.httpEntityMethodProcessor.handleReturnValue(returnValue, returnType, mavContainer, request);
81+
}
82+
83+
}

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.springframework.web.bind.annotation.ControllerAdvice;
5252
import org.springframework.web.bind.annotation.ModelAttribute;
5353
import org.springframework.web.bind.annotation.RequestBody;
54+
import org.springframework.web.bind.annotation.RequestParam;
5455
import org.springframework.web.bind.annotation.ResponseBody;
5556
import org.springframework.web.bind.annotation.SessionAttributes;
5657
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
@@ -205,6 +206,21 @@ void setReturnValueHandlers() {
205206
assertMethodProcessorCount(RESOLVER_COUNT, INIT_BINDER_RESOLVER_COUNT, 1);
206207
}
207208

209+
@Test // gh-35153
210+
void responseEntityWithWildCardAndConditionalStream() throws Exception {
211+
HandlerMethod handlerMethod = handlerMethod(new SseController(), "handle", String.class);
212+
this.handlerAdapter.afterPropertiesSet();
213+
214+
this.request.setAsyncSupported(true);
215+
this.request.addParameter("q", "sse");
216+
217+
this.handlerAdapter.handle(this.request, this.response, handlerMethod);
218+
219+
assertThat(this.response.getStatus()).isEqualTo(200);
220+
assertThat(this.response.getHeader("Content-Type")).isEqualTo("text/event-stream");
221+
assertThat(this.response.getContentAsString()).isEqualTo("data:event 1\n\ndata:event 2\n\n");
222+
}
223+
208224
@Test
209225
void modelAttributeAdvice() throws Exception {
210226
this.webAppContext.registerSingleton("maa", ModelAttributeAdvice.class);
@@ -379,6 +395,22 @@ public String handle(Model model) {
379395
}
380396

381397

398+
private static class SseController {
399+
400+
public ResponseEntity<?> handle(@RequestParam String q) throws IOException {
401+
if (q.equals("sse")) {
402+
SseEmitter emitter = new SseEmitter();
403+
emitter.send("event 1");
404+
emitter.send("event 2");
405+
emitter.complete();
406+
return ResponseEntity.ok().body(emitter);
407+
}
408+
return ResponseEntity.ok("text");
409+
}
410+
411+
}
412+
413+
382414
@ControllerAdvice
383415
private static class ModelAttributeAdvice {
384416

0 commit comments

Comments
 (0)