Skip to content

Commit 395792b

Browse files
committed
Produces media types cleared prior to error handling
Issue: SPR-16318
1 parent b3b233b commit 395792b

File tree

4 files changed

+77
-6
lines changed

4 files changed

+77
-6
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -38,6 +38,7 @@
3838
import org.springframework.web.method.HandlerMethod;
3939
import org.springframework.web.reactive.BindingContext;
4040
import org.springframework.web.reactive.HandlerAdapter;
41+
import org.springframework.web.reactive.HandlerMapping;
4142
import org.springframework.web.reactive.HandlerResult;
4243
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
4344
import org.springframework.web.server.ServerWebExchange;
@@ -206,6 +207,9 @@ private Mono<HandlerResult> handleException(Throwable exception, HandlerMethod h
206207

207208
Assert.state(this.methodResolver != null, "Not initialized");
208209

210+
// Success and error responses may use different content types
211+
exchange.getAttributes().remove(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
212+
209213
InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(exception, handlerMethod);
210214
if (invocable != null) {
211215
try {

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.web.reactive.result.method.annotation;
1818

1919
import java.io.IOException;
20+
import java.util.Collections;
21+
import java.util.Map;
2022

2123
import org.junit.Test;
2224
import org.reactivestreams.Publisher;
@@ -32,6 +34,7 @@
3234
import org.springframework.web.bind.annotation.ExceptionHandler;
3335
import org.springframework.web.bind.annotation.GetMapping;
3436
import org.springframework.web.bind.annotation.RestController;
37+
import org.springframework.web.client.HttpStatusCodeException;
3538
import org.springframework.web.reactive.config.EnableWebFlux;
3639

3740
import static org.junit.Assert.*;
@@ -74,7 +77,7 @@ public void errorBeforeFirstItem() throws Exception {
7477
}
7578

7679
@Test // SPR-16051
77-
public void exceptionAfterSeveralItems() throws Exception {
80+
public void exceptionAfterSeveralItems() {
7881
try {
7982
performGet("/SPR-16051", new HttpHeaders(), String.class).getBody();
8083
fail();
@@ -86,6 +89,21 @@ public void exceptionAfterSeveralItems() throws Exception {
8689
}
8790
}
8891

92+
@Test // SPR-16318
93+
public void exceptionFromMethodWithProducesCondition() throws Exception {
94+
try {
95+
HttpHeaders headers = new HttpHeaders();
96+
headers.add("Accept", "text/csv, application/problem+json");
97+
performGet("/SPR-16318", headers, String.class).getBody();
98+
fail();
99+
}
100+
catch (HttpStatusCodeException ex) {
101+
assertEquals(500, ex.getRawStatusCode());
102+
assertEquals("application/problem+json;charset=UTF-8", ex.getResponseHeaders().getContentType().toString());
103+
assertEquals("{\"reason\":\"error\"}", ex.getResponseBodyAsString());
104+
}
105+
}
106+
89107
private void doTest(String url, String expected) throws Exception {
90108
assertEquals(expected, performGet(url, new HttpHeaders(), String.class).getBody());
91109
}
@@ -118,7 +136,7 @@ public Publisher<String> handleAndThrowExceptionWithCauseToHandle() {
118136
throw new RuntimeException("State", new IOException("IO"));
119137
}
120138

121-
@GetMapping("/mono-error")
139+
@GetMapping(path = "/mono-error")
122140
public Publisher<String> handleWithError() {
123141
return Mono.error(new IllegalArgumentException("Argument"));
124142
}
@@ -134,6 +152,10 @@ public Flux<String> errors() {
134152
});
135153
}
136154

155+
@GetMapping(path = "/SPR-16318", produces = "text/csv")
156+
public String handleCsv() throws Exception {
157+
throw new Spr16318Exception();
158+
}
137159

138160
@ExceptionHandler
139161
public Publisher<String> handleArgumentException(IOException ex) {
@@ -149,6 +171,14 @@ public Publisher<String> handleArgumentException(IllegalArgumentException ex) {
149171
public ResponseEntity<Publisher<String>> handleStateException(IllegalStateException ex) {
150172
return ResponseEntity.ok(Mono.just("Recovered from error: " + ex.getMessage()));
151173
}
174+
175+
@ExceptionHandler
176+
public ResponseEntity<Map<String, String>> handle(Spr16318Exception ex) {
177+
return ResponseEntity.status(500).body(Collections.singletonMap("reason", "error"));
178+
}
152179
}
153180

181+
@SuppressWarnings("serial")
182+
private static class Spr16318Exception extends Exception {}
183+
154184
}

spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,9 @@ protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletExcepti
12481248
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
12491249
@Nullable Object handler, Exception ex) throws Exception {
12501250

1251+
// Success and error responses may use different content types
1252+
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
1253+
12511254
// Check registered HandlerExceptionResolvers...
12521255
ModelAndView exMv = null;
12531256
if (this.handlerExceptionResolvers != null) {

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,22 @@ public void acceptHeaders() throws Exception {
11211121

11221122
@Test
11231123
public void produces() throws Exception {
1124-
initServletWithControllers(ProducesController.class);
1124+
initServlet(wac -> {
1125+
List<HttpMessageConverter<?>> converters = new ArrayList<>();
1126+
converters.add(new MappingJackson2HttpMessageConverter());
1127+
converters.add(new Jaxb2RootElementHttpMessageConverter());
1128+
1129+
RootBeanDefinition beanDef;
1130+
1131+
beanDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
1132+
beanDef.getPropertyValues().add("messageConverters", converters);
1133+
wac.registerBeanDefinition("handlerAdapter", beanDef);
1134+
1135+
beanDef = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
1136+
beanDef.getPropertyValues().add("messageConverters", converters);
1137+
wac.registerBeanDefinition("requestMappingResolver", beanDef);
1138+
1139+
}, ProducesController.class);
11251140

11261141
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/something");
11271142
request.addHeader("Accept", "text/html");
@@ -1152,6 +1167,15 @@ public void produces() throws Exception {
11521167
response = new MockHttpServletResponse();
11531168
getServlet().service(request, response);
11541169
assertEquals(406, response.getStatus());
1170+
1171+
// SPR-16318
1172+
request = new MockHttpServletRequest("GET", "/something");
1173+
request.addHeader("Accept", "text/csv,application/problem+json");
1174+
response = new MockHttpServletResponse();
1175+
getServlet().service(request, response);
1176+
assertEquals(500, response.getStatus());
1177+
assertEquals("application/problem+json;charset=UTF-8", response.getContentType());
1178+
assertEquals("{\"reason\":\"error\"}", response.getContentAsString());
11551179
}
11561180

11571181
@Test
@@ -3000,15 +3024,25 @@ public void handleXml(Writer writer) throws IOException {
30003024
@Controller
30013025
public static class ProducesController {
30023026

3003-
@RequestMapping(value = "/something", produces = "text/html")
3027+
@GetMapping(path = "/something", produces = "text/html")
30043028
public void handleHtml(Writer writer) throws IOException {
30053029
writer.write("html");
30063030
}
30073031

3008-
@RequestMapping(value = "/something", produces = "application/xml")
3032+
@GetMapping(path = "/something", produces = "application/xml")
30093033
public void handleXml(Writer writer) throws IOException {
30103034
writer.write("xml");
30113035
}
3036+
3037+
@GetMapping(path = "/something", produces = "text/csv")
3038+
public String handleCsv() {
3039+
throw new IllegalArgumentException();
3040+
}
3041+
3042+
@ExceptionHandler
3043+
public ResponseEntity<Map<String, String>> handle(IllegalArgumentException ex) {
3044+
return ResponseEntity.status(500).body(Collections.singletonMap("reason", "error"));
3045+
}
30123046
}
30133047

30143048
@Controller

0 commit comments

Comments
 (0)