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"); | ||||
|  * 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 { | ||||
|  |  | |||
|  | @ -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 {} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue