Skip to content

Commit 9a894ab

Browse files
committed
Use ResponseEntity Content-Type as producible media type
Issue: SPR-16172
1 parent 780993c commit 9a894ab

File tree

5 files changed

+77
-10
lines changed

5 files changed

+77
-10
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/HandlerResultHandlerSupport.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ private List<MediaType> getAcceptableTypes(ServerWebExchange exchange) {
152152
private List<MediaType> getProducibleTypes(ServerWebExchange exchange,
153153
Supplier<List<MediaType>> producibleTypesSupplier) {
154154

155+
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
156+
if (contentType != null && contentType.isConcrete()) {
157+
return Collections.singletonList(contentType);
158+
}
155159
Set<MediaType> mediaTypes = exchange.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
156160
return (mediaTypes != null ? new ArrayList<>(mediaTypes) : producibleTypesSupplier.get());
157161
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ protected Mono<Void> writeBody(@Nullable Object body, MethodParameter bodyParame
111111

112112
ServerHttpRequest request = exchange.getRequest();
113113
ServerHttpResponse response = exchange.getResponse();
114-
MediaType bestMediaType = selectMediaType(exchange, () -> getProducibleMediaTypes(elementType));
114+
MediaType bestMediaType = selectMediaType(exchange, () -> getMediaTypesFor(elementType));
115115
if (bestMediaType != null) {
116116
for (HttpMessageWriter<?> writer : getMessageWriters()) {
117117
if (writer.canWrite(elementType, bestMediaType)) {
@@ -121,12 +121,12 @@ protected Mono<Void> writeBody(@Nullable Object body, MethodParameter bodyParame
121121
}
122122
}
123123
else {
124-
if (getProducibleMediaTypes(elementType).isEmpty()) {
125-
return Mono.error(new IllegalStateException("No converter for return value type: " + elementType));
124+
if (getMediaTypesFor(elementType).isEmpty()) {
125+
return Mono.error(new IllegalStateException("No writer for : " + elementType));
126126
}
127127
}
128128

129-
return Mono.error(new NotAcceptableStatusException(getProducibleMediaTypes(elementType)));
129+
return Mono.error(new NotAcceptableStatusException(getMediaTypesFor(elementType)));
130130
}
131131

132132
private ResolvableType getElementType(ReactiveAdapter adapter, ResolvableType genericType) {
@@ -141,7 +141,7 @@ else if (genericType != ResolvableType.NONE) {
141141
}
142142
}
143143

144-
private List<MediaType> getProducibleMediaTypes(ResolvableType elementType) {
144+
private List<MediaType> getMediaTypesFor(ResolvableType elementType) {
145145
return getMessageWriters().stream()
146146
.filter(converter -> converter.canWrite(elementType, null))
147147
.flatMap(converter -> converter.getWritableMediaTypes().stream())

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,16 @@ public void personResponseBodyWithMonoResponseEntity() throws Exception {
155155
assertEquals(expected, performGet("/person-response/mono-response-entity", JSON, Person.class).getBody());
156156
}
157157

158+
@Test // SPR-16172
159+
public void personResponseBodyWithMonoResponseEntityXml() throws Exception {
160+
161+
String actual = performGet("/person-response/mono-response-entity-xml",
162+
new HttpHeaders(), String.class).getBody();
163+
164+
assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
165+
"<person><name>Robert</name></person>", actual);
166+
}
167+
158168
@Test
159169
public void personResponseBodyWithList() throws Exception {
160170
List<?> expected = asList(new Person("Robert"), new Person("Marie"));
@@ -465,6 +475,12 @@ public ResponseEntity<Mono<Person>> getMonoResponseEntity() {
465475
return ResponseEntity.ok(body);
466476
}
467477

478+
@GetMapping("/mono-response-entity-xml")
479+
public ResponseEntity<Mono<Person>> getMonoResponseEntityXml() {
480+
Mono<Person> body = Mono.just(new Person("Robert"));
481+
return ResponseEntity.ok().contentType(MediaType.APPLICATION_XML).body(body);
482+
}
483+
468484
@GetMapping("/list")
469485
public List<Person> getList() {
470486
return asList(new Person("Robert"), new Person("Marie"));

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,10 @@ protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter
212212

213213
HttpServletRequest request = inputMessage.getServletRequest();
214214
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
215-
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
215+
216+
MediaType contentType = outputMessage.getHeaders().getContentType();
217+
List<MediaType> producibleMediaTypes = (contentType != null && contentType.isConcrete() ?
218+
Collections.singletonList(contentType) : getProducibleMediaTypes(request, valueType, declaredType));
216219

217220
if (outputValue != null && producibleMediaTypes.isEmpty()) {
218221
throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);

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

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@
8383
import org.springframework.http.converter.HttpMessageNotReadableException;
8484
import org.springframework.http.converter.HttpMessageNotWritableException;
8585
import org.springframework.http.converter.StringHttpMessageConverter;
86+
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
87+
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
8688
import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
8789
import org.springframework.lang.Nullable;
8890
import org.springframework.mock.web.test.MockHttpServletRequest;
@@ -934,6 +936,26 @@ public void httpEntity() throws Exception {
934936
assertEquals(404, response.getStatus());
935937
}
936938

939+
@Test // SPR-16172
940+
public void httpEntityWithContentType() throws Exception {
941+
initServlet(wac -> {
942+
RootBeanDefinition adapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
943+
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
944+
messageConverters.add(new MappingJackson2HttpMessageConverter());
945+
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
946+
adapterDef.getPropertyValues().add("messageConverters", messageConverters);
947+
wac.registerBeanDefinition("handlerAdapter", adapterDef);
948+
}, ResponseEntityController.class);
949+
950+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test-entity");
951+
MockHttpServletResponse response = new MockHttpServletResponse();
952+
getServlet().service(request, response);
953+
assertEquals(200, response.getStatus());
954+
assertEquals("application/xml", response.getHeader("Content-Type"));
955+
assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
956+
"<testEntity><name>Foo Bar</name></testEntity>", response.getContentAsString());
957+
}
958+
937959
@Test // SPR-6877
938960
public void overlappingMessageConvertersRequestBody() throws Exception {
939961
initServlet(wac -> {
@@ -3210,7 +3232,7 @@ public void templatePath(Writer writer) throws IOException {
32103232
@Controller
32113233
public static class ResponseEntityController {
32123234

3213-
@RequestMapping(path = "/foo", method = RequestMethod.POST)
3235+
@PostMapping("/foo")
32143236
public ResponseEntity<String> foo(HttpEntity<byte[]> requestEntity) throws Exception {
32153237
assertNotNull(requestEntity);
32163238
assertEquals("MyValue", requestEntity.getHeaders().getFirst("MyRequestHeader"));
@@ -3222,12 +3244,12 @@ public ResponseEntity<String> foo(HttpEntity<byte[]> requestEntity) throws Excep
32223244
return ResponseEntity.created(location).header("MyResponseHeader", "MyValue").body(body);
32233245
}
32243246

3225-
@RequestMapping(path = "/bar", method = RequestMethod.GET)
3247+
@GetMapping("/bar")
32263248
public ResponseEntity<Void> bar() {
32273249
return ResponseEntity.notFound().header("MyResponseHeader", "MyValue").build();
32283250
}
32293251

3230-
@RequestMapping(path = "/baz", method = RequestMethod.GET)
3252+
@GetMapping("/baz")
32313253
public ResponseEntity<String> baz() {
32323254
return ResponseEntity.ok().header("MyResponseHeader", "MyValue").body("body");
32333255
}
@@ -3237,10 +3259,32 @@ public ResponseEntity<Void> headResource() {
32373259
return ResponseEntity.ok().header("h1", "v1").build();
32383260
}
32393261

3240-
@RequestMapping(path = "/stores", method = RequestMethod.GET)
3262+
@GetMapping("/stores")
32413263
public ResponseEntity<String> getResource() {
32423264
return ResponseEntity.ok().body("body");
32433265
}
3266+
3267+
@GetMapping("/test-entity")
3268+
public ResponseEntity<TestEntity> testEntity() {
3269+
TestEntity entity = new TestEntity();
3270+
entity.setName("Foo Bar");
3271+
return ResponseEntity.ok().contentType(MediaType.APPLICATION_XML).body(entity);
3272+
}
3273+
}
3274+
3275+
@XmlRootElement
3276+
static class TestEntity {
3277+
3278+
private String name;
3279+
3280+
3281+
public String getName() {
3282+
return name;
3283+
}
3284+
3285+
public void setName(String name) {
3286+
this.name = name;
3287+
}
32443288
}
32453289

32463290
@Controller

0 commit comments

Comments
 (0)