Produces media types cleared prior to error handling

Issue: SPR-16318
This commit is contained in:
Rossen Stoyanchev 2018-05-14 14:41:19 -04:00
parent b3b233b43a
commit 395792b302
4 changed files with 77 additions and 6 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -38,6 +38,7 @@ import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.HandlerAdapter;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
import org.springframework.web.server.ServerWebExchange;
@ -206,6 +207,9 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
Assert.state(this.methodResolver != null, "Not initialized");
// Success and error responses may use different content types
exchange.getAttributes().remove(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(exception, handlerMethod);
if (invocable != null) {
try {

View File

@ -17,6 +17,8 @@
package org.springframework.web.reactive.result.method.annotation;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import org.junit.Test;
import org.reactivestreams.Publisher;
@ -32,6 +34,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.reactive.config.EnableWebFlux;
import static org.junit.Assert.*;
@ -74,7 +77,7 @@ public class RequestMappingExceptionHandlingIntegrationTests extends AbstractReq
}
@Test // SPR-16051
public void exceptionAfterSeveralItems() throws Exception {
public void exceptionAfterSeveralItems() {
try {
performGet("/SPR-16051", new HttpHeaders(), String.class).getBody();
fail();
@ -86,6 +89,21 @@ public class RequestMappingExceptionHandlingIntegrationTests extends AbstractReq
}
}
@Test // SPR-16318
public void exceptionFromMethodWithProducesCondition() throws Exception {
try {
HttpHeaders headers = new HttpHeaders();
headers.add("Accept", "text/csv, application/problem+json");
performGet("/SPR-16318", headers, String.class).getBody();
fail();
}
catch (HttpStatusCodeException ex) {
assertEquals(500, ex.getRawStatusCode());
assertEquals("application/problem+json;charset=UTF-8", ex.getResponseHeaders().getContentType().toString());
assertEquals("{\"reason\":\"error\"}", ex.getResponseBodyAsString());
}
}
private void doTest(String url, String expected) throws Exception {
assertEquals(expected, performGet(url, new HttpHeaders(), String.class).getBody());
}
@ -118,7 +136,7 @@ public class RequestMappingExceptionHandlingIntegrationTests extends AbstractReq
throw new RuntimeException("State", new IOException("IO"));
}
@GetMapping("/mono-error")
@GetMapping(path = "/mono-error")
public Publisher<String> handleWithError() {
return Mono.error(new IllegalArgumentException("Argument"));
}
@ -134,6 +152,10 @@ public class RequestMappingExceptionHandlingIntegrationTests extends AbstractReq
});
}
@GetMapping(path = "/SPR-16318", produces = "text/csv")
public String handleCsv() throws Exception {
throw new Spr16318Exception();
}
@ExceptionHandler
public Publisher<String> handleArgumentException(IOException ex) {
@ -149,6 +171,14 @@ public class RequestMappingExceptionHandlingIntegrationTests extends AbstractReq
public ResponseEntity<Publisher<String>> handleStateException(IllegalStateException ex) {
return ResponseEntity.ok(Mono.just("Recovered from error: " + ex.getMessage()));
}
@ExceptionHandler
public ResponseEntity<Map<String, String>> handle(Spr16318Exception ex) {
return ResponseEntity.status(500).body(Collections.singletonMap("reason", "error"));
}
}
@SuppressWarnings("serial")
private static class Spr16318Exception extends Exception {}
}

View File

@ -1248,6 +1248,9 @@ public class DispatcherServlet extends FrameworkServlet {
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {

View File

@ -1121,7 +1121,22 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Test
public void produces() throws Exception {
initServletWithControllers(ProducesController.class);
initServlet(wac -> {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(new MappingJackson2HttpMessageConverter());
converters.add(new Jaxb2RootElementHttpMessageConverter());
RootBeanDefinition beanDef;
beanDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
beanDef.getPropertyValues().add("messageConverters", converters);
wac.registerBeanDefinition("handlerAdapter", beanDef);
beanDef = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
beanDef.getPropertyValues().add("messageConverters", converters);
wac.registerBeanDefinition("requestMappingResolver", beanDef);
}, ProducesController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/something");
request.addHeader("Accept", "text/html");
@ -1152,6 +1167,15 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(406, response.getStatus());
// SPR-16318
request = new MockHttpServletRequest("GET", "/something");
request.addHeader("Accept", "text/csv,application/problem+json");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(500, response.getStatus());
assertEquals("application/problem+json;charset=UTF-8", response.getContentType());
assertEquals("{\"reason\":\"error\"}", response.getContentAsString());
}
@Test
@ -3000,15 +3024,25 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Controller
public static class ProducesController {
@RequestMapping(value = "/something", produces = "text/html")
@GetMapping(path = "/something", produces = "text/html")
public void handleHtml(Writer writer) throws IOException {
writer.write("html");
}
@RequestMapping(value = "/something", produces = "application/xml")
@GetMapping(path = "/something", produces = "application/xml")
public void handleXml(Writer writer) throws IOException {
writer.write("xml");
}
@GetMapping(path = "/something", produces = "text/csv")
public String handleCsv() {
throw new IllegalArgumentException();
}
@ExceptionHandler
public ResponseEntity<Map<String, String>> handle(IllegalArgumentException ex) {
return ResponseEntity.status(500).body(Collections.singletonMap("reason", "error"));
}
}
@Controller