Produces media types cleared prior to error handling
Issue: SPR-16318
This commit is contained in:
		
							parent
							
								
									b3b233b43a
								
							
						
					
					
						commit
						395792b302
					
				|  | @ -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"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with 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.method.HandlerMethod; | ||||||
| import org.springframework.web.reactive.BindingContext; | import org.springframework.web.reactive.BindingContext; | ||||||
| import org.springframework.web.reactive.HandlerAdapter; | import org.springframework.web.reactive.HandlerAdapter; | ||||||
|  | import org.springframework.web.reactive.HandlerMapping; | ||||||
| import org.springframework.web.reactive.HandlerResult; | import org.springframework.web.reactive.HandlerResult; | ||||||
| import org.springframework.web.reactive.result.method.InvocableHandlerMethod; | import org.springframework.web.reactive.result.method.InvocableHandlerMethod; | ||||||
| import org.springframework.web.server.ServerWebExchange; | import org.springframework.web.server.ServerWebExchange; | ||||||
|  | @ -206,6 +207,9 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application | ||||||
| 
 | 
 | ||||||
| 		Assert.state(this.methodResolver != null, "Not initialized"); | 		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); | 		InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(exception, handlerMethod); | ||||||
| 		if (invocable != null) { | 		if (invocable != null) { | ||||||
| 			try { | 			try { | ||||||
|  |  | ||||||
|  | @ -17,6 +17,8 @@ | ||||||
| package org.springframework.web.reactive.result.method.annotation; | package org.springframework.web.reactive.result.method.annotation; | ||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.Map; | ||||||
| 
 | 
 | ||||||
| import org.junit.Test; | import org.junit.Test; | ||||||
| import org.reactivestreams.Publisher; | 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.ExceptionHandler; | ||||||
| import org.springframework.web.bind.annotation.GetMapping; | import org.springframework.web.bind.annotation.GetMapping; | ||||||
| import org.springframework.web.bind.annotation.RestController; | import org.springframework.web.bind.annotation.RestController; | ||||||
|  | import org.springframework.web.client.HttpStatusCodeException; | ||||||
| import org.springframework.web.reactive.config.EnableWebFlux; | import org.springframework.web.reactive.config.EnableWebFlux; | ||||||
| 
 | 
 | ||||||
| import static org.junit.Assert.*; | import static org.junit.Assert.*; | ||||||
|  | @ -74,7 +77,7 @@ public class RequestMappingExceptionHandlingIntegrationTests extends AbstractReq | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@Test // SPR-16051 | 	@Test // SPR-16051 | ||||||
| 	public void exceptionAfterSeveralItems() throws Exception { | 	public void exceptionAfterSeveralItems() { | ||||||
| 		try { | 		try { | ||||||
| 			performGet("/SPR-16051", new HttpHeaders(), String.class).getBody(); | 			performGet("/SPR-16051", new HttpHeaders(), String.class).getBody(); | ||||||
| 			fail(); | 			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 { | 	private void doTest(String url, String expected) throws Exception { | ||||||
| 		assertEquals(expected, performGet(url, new HttpHeaders(), String.class).getBody()); | 		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")); | 			throw new RuntimeException("State", new IOException("IO")); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		@GetMapping("/mono-error") | 		@GetMapping(path = "/mono-error") | ||||||
| 		public Publisher<String> handleWithError() { | 		public Publisher<String> handleWithError() { | ||||||
| 			return Mono.error(new IllegalArgumentException("Argument")); | 			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 | 		@ExceptionHandler | ||||||
| 		public Publisher<String> handleArgumentException(IOException ex) { | 		public Publisher<String> handleArgumentException(IOException ex) { | ||||||
|  | @ -149,6 +171,14 @@ public class RequestMappingExceptionHandlingIntegrationTests extends AbstractReq | ||||||
| 		public ResponseEntity<Publisher<String>> handleStateException(IllegalStateException ex) { | 		public ResponseEntity<Publisher<String>> handleStateException(IllegalStateException ex) { | ||||||
| 			return ResponseEntity.ok(Mono.just("Recovered from error: " + ex.getMessage())); | 			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 {} | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1248,6 +1248,9 @@ public class DispatcherServlet extends FrameworkServlet { | ||||||
| 	protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, | 	protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, | ||||||
| 			@Nullable Object handler, Exception ex) throws Exception { | 			@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... | 		// Check registered HandlerExceptionResolvers... | ||||||
| 		ModelAndView exMv = null; | 		ModelAndView exMv = null; | ||||||
| 		if (this.handlerExceptionResolvers != null) { | 		if (this.handlerExceptionResolvers != null) { | ||||||
|  |  | ||||||
|  | @ -1121,7 +1121,22 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl | ||||||
| 
 | 
 | ||||||
| 	@Test | 	@Test | ||||||
| 	public void produces() throws Exception { | 	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"); | 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/something"); | ||||||
| 		request.addHeader("Accept", "text/html"); | 		request.addHeader("Accept", "text/html"); | ||||||
|  | @ -1152,6 +1167,15 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl | ||||||
| 		response = new MockHttpServletResponse(); | 		response = new MockHttpServletResponse(); | ||||||
| 		getServlet().service(request, response); | 		getServlet().service(request, response); | ||||||
| 		assertEquals(406, response.getStatus()); | 		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 | 	@Test | ||||||
|  | @ -3000,15 +3024,25 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl | ||||||
| 	@Controller | 	@Controller | ||||||
| 	public static class ProducesController { | 	public static class ProducesController { | ||||||
| 
 | 
 | ||||||
| 		@RequestMapping(value = "/something", produces = "text/html") | 		@GetMapping(path = "/something", produces = "text/html") | ||||||
| 		public void handleHtml(Writer writer) throws IOException { | 		public void handleHtml(Writer writer) throws IOException { | ||||||
| 			writer.write("html"); | 			writer.write("html"); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		@RequestMapping(value = "/something", produces = "application/xml") | 		@GetMapping(path = "/something", produces = "application/xml") | ||||||
| 		public void handleXml(Writer writer) throws IOException { | 		public void handleXml(Writer writer) throws IOException { | ||||||
| 			writer.write("xml"); | 			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 | 	@Controller | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue