Skip to content

Commit 817136a

Browse files
committed
Merge branch '6.2.x'
2 parents 6a3311b + 55634f9 commit 817136a

File tree

4 files changed

+57
-14
lines changed

4 files changed

+57
-14
lines changed

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ public boolean isReactiveType(Class<?> type) {
135135
* @return an emitter for streaming, or {@code null} if handled internally
136136
* with a {@link DeferredResult}
137137
*/
138-
public @Nullable ResponseBodyEmitter handleValue(Object returnValue, MethodParameter returnType,
138+
public @Nullable ResponseBodyEmitter handleValue(
139+
Object returnValue, MethodParameter returnType, @Nullable MediaType presetContentType,
139140
ModelAndViewContainer mav, NativeWebRequest request) throws Exception {
140141

141142
Assert.notNull(returnValue, "Expected return value");
@@ -154,7 +155,7 @@ public boolean isReactiveType(Class<?> type) {
154155
ResolvableType elementType = ResolvableType.forMethodParameter(returnType).getGeneric();
155156
Class<?> elementClass = elementType.toClass();
156157

157-
Collection<MediaType> mediaTypes = getMediaTypes(request);
158+
Collection<MediaType> mediaTypes = getMediaTypes(request, presetContentType);
158159
Optional<MediaType> mediaType = mediaTypes.stream().filter(MimeType::isConcrete).findFirst();
159160

160161
if (adapter.isMultiValue()) {
@@ -220,14 +221,25 @@ else if (MediaType.APPLICATION_NDJSON.includes(acceptedType)) {
220221
}
221222

222223
@SuppressWarnings("unchecked")
223-
private Collection<MediaType> getMediaTypes(NativeWebRequest request)
224+
private Collection<MediaType> getMediaTypes(NativeWebRequest request, @Nullable MediaType contentType)
224225
throws HttpMediaTypeNotAcceptableException {
225226

226-
Collection<MediaType> mediaTypes = (Collection<MediaType>) request.getAttribute(
227+
Collection<MediaType> producibleMediaTypes = (Collection<MediaType>) request.getAttribute(
227228
HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
228229

229-
return CollectionUtils.isEmpty(mediaTypes) ?
230-
this.contentNegotiationManager.resolveMediaTypes(request) : mediaTypes;
230+
Collection<MediaType> mediaTypes = (CollectionUtils.isEmpty(producibleMediaTypes) ?
231+
this.contentNegotiationManager.resolveMediaTypes(request) : producibleMediaTypes);
232+
233+
if (contentType != null) {
234+
for (MediaType mediaType : mediaTypes) {
235+
if (mediaType.isConcrete()) {
236+
return mediaTypes;
237+
}
238+
}
239+
return List.of(contentType);
240+
}
241+
242+
return mediaTypes;
231243
}
232244

233245
private ResponseBodyEmitter getEmitter(MediaType mediaType) {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,12 @@ public void handleReturnValue(@Nullable Object returnValue, MethodParameter retu
186186
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
187187
Assert.state(response != null, "No HttpServletResponse");
188188
ServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
189+
MediaType contentType = null;
189190

190191
if (returnValue instanceof ResponseEntity<?> responseEntity) {
191192
response.setStatus(responseEntity.getStatusCode().value());
192193
outputMessage.getHeaders().putAll(responseEntity.getHeaders());
194+
contentType = responseEntity.getHeaders().getContentType();
193195
returnValue = responseEntity.getBody();
194196
returnType = returnType.nested();
195197
if (returnValue == null) {
@@ -207,7 +209,7 @@ public void handleReturnValue(@Nullable Object returnValue, MethodParameter retu
207209
emitter = responseBodyEmitter;
208210
}
209211
else {
210-
emitter = this.reactiveHandler.handleValue(returnValue, returnType, mavContainer, webRequest);
212+
emitter = this.reactiveHandler.handleValue(returnValue, returnType, contentType, mavContainer, webRequest);
211213
if (emitter == null) {
212214
// We're not streaming; write headers without committing response
213215
outputMessage.getHeaders().forEach((headerName, headerValues) -> {

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -229,19 +229,22 @@ void mediaTypes() throws Exception {
229229

230230
// Media type from request
231231
this.servletRequest.addHeader("Accept", "text/event-stream");
232-
testSseResponse(true);
232+
testSseResponse(true, null);
233233

234234
// Media type from "produces" attribute
235235
Set<MediaType> types = Collections.singleton(MediaType.TEXT_EVENT_STREAM);
236236
this.servletRequest.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, types);
237-
testSseResponse(true);
237+
testSseResponse(true, null);
238+
239+
// Preset media type // gh-35130
240+
testSseResponse(true, MediaType.TEXT_EVENT_STREAM);
238241

239242
// No media type preferences
240-
testSseResponse(false);
243+
testSseResponse(false, null);
241244
}
242245

243-
private void testSseResponse(boolean expectSseEmitter) throws Exception {
244-
ResponseBodyEmitter emitter = handleValue(Flux.empty(), Flux.class, forClass(String.class));
246+
private void testSseResponse(boolean expectSseEmitter, @Nullable MediaType contentType) throws Exception {
247+
ResponseBodyEmitter emitter = handleValue(Flux.empty(), Flux.class, forClass(String.class), contentType);
245248
Object actual = emitter instanceof SseEmitter;
246249
assertThat(actual).isEqualTo(expectSseEmitter);
247250
resetRequest();
@@ -437,7 +440,7 @@ void contextPropagation() throws Exception {
437440

438441
try {
439442
Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();
440-
ResponseBodyEmitter emitter = handler.handleValue(sink.asFlux(), returnType, mavContainer, this.webRequest);
443+
ResponseBodyEmitter emitter = handler.handleValue(sink.asFlux(), returnType, null, mavContainer, this.webRequest);
441444

442445
ContextEmitterHandler emitterHandler = new ContextEmitterHandler();
443446
emitter.initialize(emitterHandler);
@@ -484,9 +487,15 @@ private void testDeferredResultSubscriber(Object returnValue, Class<?> asyncType
484487
private ResponseBodyEmitter handleValue(Object returnValue, Class<?> asyncType,
485488
ResolvableType genericType) throws Exception {
486489

490+
return handleValue(returnValue, asyncType, genericType, null);
491+
}
492+
493+
private ResponseBodyEmitter handleValue(Object returnValue, Class<?> asyncType,
494+
ResolvableType genericType, @Nullable MediaType contentType) throws Exception {
495+
487496
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
488497
MethodParameter returnType = on(TestController.class).resolveReturnType(asyncType, genericType);
489-
return this.handler.handleValue(returnValue, returnType, mavContainer, this.webRequest);
498+
return this.handler.handleValue(returnValue, returnType, contentType, mavContainer, this.webRequest);
490499
}
491500

492501

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828
import io.micrometer.context.ContextSnapshot.Scope;
2929
import org.junit.jupiter.api.BeforeEach;
3030
import org.junit.jupiter.api.Test;
31+
import org.reactivestreams.Publisher;
3132
import reactor.core.publisher.Flux;
3233
import reactor.core.publisher.Sinks;
3334
import reactor.core.scheduler.Schedulers;
3435

3536
import org.springframework.core.MethodParameter;
3637
import org.springframework.core.ResolvableType;
38+
import org.springframework.http.MediaType;
3739
import org.springframework.http.ResponseEntity;
3840
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
3941
import org.springframework.web.context.request.NativeWebRequest;
@@ -342,6 +344,21 @@ void responseEntityFluxWithCustomHeader() throws Exception {
342344
assertThat(this.response.isCommitted()).isFalse();
343345
}
344346

347+
@Test // gh-35130
348+
void responseEntityFluxSseWithPresetContentType() throws Exception {
349+
350+
ResponseEntity<Publisher<?>> entity =
351+
ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(Flux.just("foo", "bar"));
352+
353+
MethodParameter type = on(TestController.class).resolveReturnType(ResponseEntity.class, Publisher.class);
354+
this.handler.handleReturnValue(entity, type, this.mavContainer, this.webRequest);
355+
356+
assertThat(this.request.isAsyncStarted()).isTrue();
357+
assertThat(this.response.getStatus()).isEqualTo(200);
358+
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
359+
assertThat(this.response.getContentAsString()).isEqualTo("data:foo\n\ndata:bar\n\n");
360+
}
361+
345362

346363
@SuppressWarnings({"unused", "ConstantConditions"})
347364
private static class TestController {
@@ -365,6 +382,9 @@ private static class TestController {
365382
private ResponseEntity<Flux<String>> h9() { return null; }
366383

367384
private ResponseEntity<Flux<SimpleBean>> h10() { return null; }
385+
386+
private ResponseEntity<Publisher<?>> h11() { return null; }
387+
368388
}
369389

370390

0 commit comments

Comments
 (0)