Commit 3f88906b authored by Stephane Nicoll's avatar Stephane Nicoll

Fix handling of ResponseStatusException

This commit updates DefaultErrorAttributes to handle
ResponseStatusException explicitly. This exception is used in a
WebFlux application to signal that the processing of the query has
failed with an HTTP status code and a reason phrase. The latter is now
properly mapped to the `message` attribute of the response body.

Closes gh-11614
parent fdd501c9
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-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.
...@@ -25,6 +25,7 @@ import java.util.Map; ...@@ -25,6 +25,7 @@ import java.util.Map;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError; import org.springframework.validation.ObjectError;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
...@@ -44,6 +45,7 @@ import org.springframework.web.server.ServerWebExchange; ...@@ -44,6 +45,7 @@ import org.springframework.web.server.ServerWebExchange;
* </ul> * </ul>
* *
* @author Brian Clozel * @author Brian Clozel
* @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
* @see ErrorAttributes * @see ErrorAttributes
*/ */
...@@ -77,24 +79,36 @@ public class DefaultErrorAttributes implements ErrorAttributes { ...@@ -77,24 +79,36 @@ public class DefaultErrorAttributes implements ErrorAttributes {
errorAttributes.put("timestamp", new Date()); errorAttributes.put("timestamp", new Date());
errorAttributes.put("path", request.path()); errorAttributes.put("path", request.path());
Throwable error = getError(request); Throwable error = getError(request);
if (this.includeException) { HttpStatus errorStatus = determineHttpStatus(error);
errorAttributes.put("exception", error.getClass().getName()); errorAttributes.put("status", errorStatus.value());
errorAttributes.put("error", errorStatus.getReasonPhrase());
errorAttributes.put("message", determineMessage(error));
handleException(errorAttributes, determineException(error), includeStackTrace);
return errorAttributes;
}
private HttpStatus determineHttpStatus(Throwable error) {
if (error instanceof ResponseStatusException) {
return ((ResponseStatusException) error).getStatus();
} }
if (includeStackTrace) { return HttpStatus.INTERNAL_SERVER_ERROR;
addStackTrace(errorAttributes, error); }
private String determineMessage(Throwable error) {
if (error instanceof WebExchangeBindException) {
return error.getMessage();
} }
addErrorMessage(errorAttributes, error);
if (error instanceof ResponseStatusException) { if (error instanceof ResponseStatusException) {
HttpStatus errorStatus = ((ResponseStatusException) error).getStatus(); return ((ResponseStatusException) error).getReason();
errorAttributes.put("status", errorStatus.value());
errorAttributes.put("error", errorStatus.getReasonPhrase());
} }
else { return error.getMessage();
errorAttributes.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value()); }
errorAttributes.put("error",
HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()); private Throwable determineException(Throwable error) {
if (error instanceof ResponseStatusException) {
return error.getCause() != null ? error.getCause() : error;
} }
return errorAttributes; return error;
} }
private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) { private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
...@@ -104,8 +118,14 @@ public class DefaultErrorAttributes implements ErrorAttributes { ...@@ -104,8 +118,14 @@ public class DefaultErrorAttributes implements ErrorAttributes {
errorAttributes.put("trace", stackTrace.toString()); errorAttributes.put("trace", stackTrace.toString());
} }
private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) { private void handleException(Map<String, Object> errorAttributes,
errorAttributes.put("message", error.getMessage()); Throwable error, boolean includeStackTrace) {
if (this.includeException) {
errorAttributes.put("exception", error.getClass().getName());
}
if (includeStackTrace) {
addStackTrace(errorAttributes, error);
}
if (error instanceof BindingResult) { if (error instanceof BindingResult) {
BindingResult result = (BindingResult) error; BindingResult result = (BindingResult) error;
if (result.getErrorCount() > 0) { if (result.getErrorCount() > 0) {
......
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-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.
...@@ -46,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -46,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link DefaultErrorAttributes}. * Tests for {@link DefaultErrorAttributes}.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Stephane Nicoll
*/ */
public class DefaultErrorAttributesTests { public class DefaultErrorAttributesTests {
...@@ -125,6 +126,39 @@ public class DefaultErrorAttributesTests { ...@@ -125,6 +126,39 @@ public class DefaultErrorAttributesTests {
assertThat(attributes.get("message")).isEqualTo("Test"); assertThat(attributes.get("message")).isEqualTo("Test");
} }
@Test
public void processResponseStatusException() {
RuntimeException nested = new RuntimeException("Test");
ResponseStatusException error = new ResponseStatusException(
HttpStatus.BAD_REQUEST, "invalid request", nested);
this.errorAttributes = new DefaultErrorAttributes(true);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes
.getErrorAttributes(serverRequest, false);
assertThat(attributes.get("status")).isEqualTo(400);
assertThat(attributes.get("message")).isEqualTo("invalid request");
assertThat(attributes.get("exception"))
.isEqualTo(RuntimeException.class.getName());
assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error);
}
@Test
public void processResponseStatusExceptionWithNoNestedCause() {
ResponseStatusException error = new ResponseStatusException(
HttpStatus.NOT_ACCEPTABLE, "could not process request");
this.errorAttributes = new DefaultErrorAttributes(true);
MockServerHttpRequest request = MockServerHttpRequest.get("/test").build();
ServerRequest serverRequest = buildServerRequest(request, error);
Map<String, Object> attributes = this.errorAttributes
.getErrorAttributes(serverRequest, false);
assertThat(attributes.get("status")).isEqualTo(406);
assertThat(attributes.get("message")).isEqualTo("could not process request");
assertThat(attributes.get("exception"))
.isEqualTo(ResponseStatusException.class.getName());
assertThat(this.errorAttributes.getError(serverRequest)).isSameAs(error);
}
@Test @Test
public void notIncludeTrace() { public void notIncludeTrace() {
RuntimeException ex = new RuntimeException("Test"); RuntimeException ex = new RuntimeException("Test");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment