Skip to content

Commit 34d3240

Browse files
committed
Return 500 if producible attribute present
When a request is mapped through a producible condition on an @RequestMapping, then a failure to find a converter/decoder should be a 500 because the return type + media type pair were declared by the controller and that should be possible to render. Closes gh-23287
1 parent 1ec15ba commit 34d3240

File tree

4 files changed

+57
-7
lines changed

4 files changed

+57
-7
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -19,6 +19,7 @@
1919
import java.lang.reflect.Method;
2020
import java.util.ArrayList;
2121
import java.util.List;
22+
import java.util.Set;
2223

2324
import kotlin.reflect.KFunction;
2425
import kotlin.reflect.jvm.ReflectJvmMapping;
@@ -36,6 +37,8 @@
3637
import org.springframework.http.converter.HttpMessageNotWritableException;
3738
import org.springframework.lang.Nullable;
3839
import org.springframework.util.Assert;
40+
import org.springframework.util.CollectionUtils;
41+
import org.springframework.web.reactive.HandlerMapping;
3942
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
4043
import org.springframework.web.reactive.result.HandlerResultHandlerSupport;
4144
import org.springframework.web.server.NotAcceptableStatusException;
@@ -163,7 +166,9 @@ protected Mono<Void> writeBody(@Nullable Object body, MethodParameter bodyParame
163166
}
164167

165168
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
166-
if (contentType != null && contentType.equals(bestMediaType)) {
169+
boolean isPresentMediaType = (contentType != null && contentType.equals(bestMediaType));
170+
Set<MediaType> producibleTypes = exchange.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
171+
if (isPresentMediaType || !CollectionUtils.isEmpty(producibleTypes)) {
167172
return Mono.error(new HttpMessageNotWritableException(
168173
"No Encoder for [" + elementType + "] with preset Content-Type '" + contentType + "'"));
169174
}

spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -26,6 +26,7 @@
2626
import java.util.Collections;
2727
import java.util.LinkedHashSet;
2828
import java.util.List;
29+
import java.util.Set;
2930
import java.util.concurrent.CompletableFuture;
3031

3132
import org.junit.jupiter.api.BeforeEach;
@@ -371,6 +372,28 @@ public void handleWithPresetContentTypeShouldFailWithServerError() {
371372
.verify();
372373
}
373374

375+
@Test // gh-23287
376+
public void handleWithProducibleContentTypeShouldFailWithServerError() {
377+
ResponseEntity<String> value = ResponseEntity.ok().body("<foo/>");
378+
MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class));
379+
HandlerResult result = handlerResult(value, returnType);
380+
381+
MockServerWebExchange exchange = MockServerWebExchange.from(get("/path"));
382+
Set<MediaType> mediaTypes = Collections.singleton(MediaType.APPLICATION_XML);
383+
exchange.getAttributes().put(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
384+
385+
ResponseEntityResultHandler resultHandler = new ResponseEntityResultHandler(
386+
Collections.singletonList(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly())),
387+
new RequestedContentTypeResolverBuilder().build()
388+
);
389+
390+
StepVerifier.create(resultHandler.handleResult(exchange, result))
391+
.consumeErrorWith(ex -> assertThat(ex)
392+
.isInstanceOf(HttpMessageNotWritableException.class)
393+
.hasMessageContaining("with preset Content-Type"))
394+
.verify();
395+
}
396+
374397

375398
private void testHandle(Object returnValue, MethodParameter returnType) {
376399
MockServerWebExchange exchange = MockServerWebExchange.from(get("/path"));

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -309,7 +309,11 @@ else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
309309
}
310310

311311
if (body != null) {
312-
if (isContentTypePreset) {
312+
Set<MediaType> producibleMediaTypes =
313+
(Set<MediaType>) inputMessage.getServletRequest()
314+
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
315+
316+
if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
313317
throw new HttpMessageNotWritableException(
314318
"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
315319
}

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -28,6 +28,7 @@
2828
import java.util.Arrays;
2929
import java.util.Collections;
3030
import java.util.Date;
31+
import java.util.Set;
3132

3233
import org.junit.jupiter.api.BeforeEach;
3334
import org.junit.jupiter.api.Test;
@@ -323,7 +324,24 @@ public void shouldFailWithServerErrorIfContentTypeFromResponseEntity() {
323324
.contentType(MediaType.APPLICATION_XML)
324325
.body("<foo/>");
325326

326-
given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
327+
given(stringHttpMessageConverter.canWrite(String.class, TEXT_PLAIN)).willReturn(true);
328+
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(TEXT_PLAIN));
329+
330+
assertThatThrownBy(() ->
331+
processor.handleReturnValue(
332+
returnValue, returnTypeResponseEntity, mavContainer, webRequest))
333+
.isInstanceOf(HttpMessageNotWritableException.class)
334+
.hasMessageContaining("with preset Content-Type");
335+
}
336+
337+
@Test // gh-23287
338+
public void shouldFailWithServerErrorIfContentTypeFromProducibleAttribute() {
339+
Set<MediaType> mediaTypes = Collections.singleton(MediaType.APPLICATION_XML);
340+
servletRequest.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
341+
342+
ResponseEntity<String> returnValue = ResponseEntity.ok().body("<foo/>");
343+
344+
given(stringHttpMessageConverter.canWrite(String.class, TEXT_PLAIN)).willReturn(true);
327345
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(TEXT_PLAIN));
328346

329347
assertThatThrownBy(() ->

0 commit comments

Comments
 (0)