Support for custom status in ResponseStatusException

Closes gh-20336
This commit is contained in:
Rossen Stoyanchev 2020-07-09 15:06:33 +03:00
parent 4d7418841c
commit 37366e0c91
5 changed files with 68 additions and 29 deletions

View File

@ -36,7 +36,7 @@ import org.springframework.util.Assert;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class ResponseStatusException extends NestedRuntimeException { public class ResponseStatusException extends NestedRuntimeException {
private final HttpStatus status; private final int status;
@Nullable @Nullable
private final String reason; private final String reason;
@ -70,15 +70,44 @@ public class ResponseStatusException extends NestedRuntimeException {
public ResponseStatusException(HttpStatus status, @Nullable String reason, @Nullable Throwable cause) { public ResponseStatusException(HttpStatus status, @Nullable String reason, @Nullable Throwable cause) {
super(null, cause); super(null, cause);
Assert.notNull(status, "HttpStatus is required"); Assert.notNull(status, "HttpStatus is required");
this.status = status; this.status = status.value();
this.reason = reason;
}
/**
* Constructor with a response status and a reason to add to the exception
* message as explanation, as well as a nested exception.
* @param rawStatusCode the HTTP status code value
* @param reason the associated reason (optional)
* @param cause a nested exception (optional)
* @since 5.3
*/
public ResponseStatusException(int rawStatusCode, @Nullable String reason, @Nullable Throwable cause) {
super(null, cause);
this.status = rawStatusCode;
this.reason = reason; this.reason = reason;
} }
/** /**
* Return the HTTP status associated with this exception. * Return the HTTP status associated with this exception.
* @throws IllegalArgumentException in case of an unknown HTTP status code
* @since #getRawStatusCode()
* @see HttpStatus#valueOf(int)
*/ */
public HttpStatus getStatus() { public HttpStatus getStatus() {
return HttpStatus.valueOf(this.status);
}
/**
* Return the HTTP status code (potentially non-standard and not resolvable
* through the {@link HttpStatus} enum) as an integer.
* @return the HTTP status as an integer value
* @since 5.3
* @see #getStatus()
* @see HttpStatus#resolve(int)
*/
public int getRawStatusCode() {
return this.status; return this.status;
} }
@ -121,7 +150,8 @@ public class ResponseStatusException extends NestedRuntimeException {
@Override @Override
public String getMessage() { public String getMessage() {
String msg = this.status + (this.reason != null ? " \"" + this.reason + "\"" : ""); HttpStatus code = HttpStatus.resolve(this.status);
String msg = (code != null ? code : this.status) + (this.reason != null ? " \"" + this.reason + "\"" : "");
return NestedExceptionUtils.buildMessage(msg, getCause()); return NestedExceptionUtils.buildMessage(msg, getCause());
} }

View File

@ -88,9 +88,10 @@ public class ResponseStatusExceptionHandler implements WebExceptionHandler {
private boolean updateResponse(ServerHttpResponse response, Throwable ex) { private boolean updateResponse(ServerHttpResponse response, Throwable ex) {
boolean result = false; boolean result = false;
HttpStatus status = determineStatus(ex); HttpStatus httpStatus = determineStatus(ex);
if (status != null) { int code = (httpStatus != null ? httpStatus.value() : determineRawStatusCode(ex));
if (response.setStatusCode(status)) { if (code != -1) {
if (response.setRawStatusCode(code)) {
if (ex instanceof ResponseStatusException) { if (ex instanceof ResponseStatusException) {
((ResponseStatusException) ex).getResponseHeaders() ((ResponseStatusException) ex).getResponseHeaders()
.forEach((name, values) -> .forEach((name, values) ->
@ -109,17 +110,30 @@ public class ResponseStatusExceptionHandler implements WebExceptionHandler {
} }
/** /**
* Determine the HTTP status implied by the given exception. * Determine the HTTP status for the given exception.
* @param ex the exception to introspect * <p>As of 5.3 this method always returns {@code null} in which case
* {@link #determineRawStatusCode(Throwable)} is used instead.
* @param ex the exception to check
* @return the associated HTTP status, if any * @return the associated HTTP status, if any
* @since 5.0.5 * @deprecated as of 5.3 in favor of {@link #determineRawStatusCode(Throwable)}.
*/ */
@Nullable @Nullable
@Deprecated
protected HttpStatus determineStatus(Throwable ex) { protected HttpStatus determineStatus(Throwable ex) {
if (ex instanceof ResponseStatusException) {
return ((ResponseStatusException) ex).getStatus();
}
return null; return null;
} }
/**
* Determine the raw status code for the given exception.
* @param ex the exception to check
* @return the associated HTTP status code, or -1 if it can't be derived.
* @since 5.3
*/
protected int determineRawStatusCode(Throwable ex) {
if (ex instanceof ResponseStatusException) {
return ((ResponseStatusException) ex).getRawStatusCode();
}
return -1;
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2020 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.
@ -17,8 +17,6 @@
package org.springframework.web.reactive.handler; package org.springframework.web.reactive.handler;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.server.handler.ResponseStatusExceptionHandler; import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
@ -39,13 +37,12 @@ import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
public class WebFluxResponseStatusExceptionHandler extends ResponseStatusExceptionHandler { public class WebFluxResponseStatusExceptionHandler extends ResponseStatusExceptionHandler {
@Override @Override
@Nullable protected int determineRawStatusCode(Throwable ex) {
protected HttpStatus determineStatus(Throwable ex) { int status = super.determineRawStatusCode(ex);
HttpStatus status = super.determineStatus(ex); if (status == -1) {
if (status == null) {
ResponseStatus ann = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); ResponseStatus ann = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
if (ann != null) { if (ann != null) {
status = ann.code(); status = ann.code().value();
} }
} }
return status; return status;

View File

@ -275,7 +275,7 @@ public class ResourceWebHandlerTests {
StepVerifier.create(handler.handle(exchange)) StepVerifier.create(handler.handle(exchange))
.expectErrorSatisfies(err -> { .expectErrorSatisfies(err -> {
assertThat(err).isInstanceOf(ResponseStatusException.class); assertThat(err).isInstanceOf(ResponseStatusException.class);
assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404);
}).verify(TIMEOUT); }).verify(TIMEOUT);
} }
@ -321,7 +321,7 @@ public class ResourceWebHandlerTests {
StepVerifier.create(this.handler.handle(exchange)) StepVerifier.create(this.handler.handle(exchange))
.expectErrorSatisfies(err -> { .expectErrorSatisfies(err -> {
assertThat(err).isInstanceOf(ResponseStatusException.class); assertThat(err).isInstanceOf(ResponseStatusException.class);
assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404);
}) })
.verify(TIMEOUT); .verify(TIMEOUT);
if (!location.createRelative(requestPath).exists() && !requestPath.contains(":")) { if (!location.createRelative(requestPath).exists() && !requestPath.contains(":")) {
@ -416,7 +416,7 @@ public class ResourceWebHandlerTests {
StepVerifier.create(this.handler.handle(exchange)) StepVerifier.create(this.handler.handle(exchange))
.expectErrorSatisfies(err -> { .expectErrorSatisfies(err -> {
assertThat(err).isInstanceOf(ResponseStatusException.class); assertThat(err).isInstanceOf(ResponseStatusException.class);
assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404);
}).verify(TIMEOUT); }).verify(TIMEOUT);
} }
@ -427,7 +427,7 @@ public class ResourceWebHandlerTests {
StepVerifier.create(this.handler.handle(exchange)) StepVerifier.create(this.handler.handle(exchange))
.expectErrorSatisfies(err -> { .expectErrorSatisfies(err -> {
assertThat(err).isInstanceOf(ResponseStatusException.class); assertThat(err).isInstanceOf(ResponseStatusException.class);
assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404);
}).verify(TIMEOUT); }).verify(TIMEOUT);
} }
@ -438,7 +438,7 @@ public class ResourceWebHandlerTests {
StepVerifier.create(this.handler.handle(exchange)) StepVerifier.create(this.handler.handle(exchange))
.expectErrorSatisfies(err -> { .expectErrorSatisfies(err -> {
assertThat(err).isInstanceOf(ResponseStatusException.class); assertThat(err).isInstanceOf(ResponseStatusException.class);
assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404);
}).verify(TIMEOUT); }).verify(TIMEOUT);
} }
@ -473,7 +473,7 @@ public class ResourceWebHandlerTests {
StepVerifier.create(mono) StepVerifier.create(mono)
.expectErrorSatisfies(err -> { .expectErrorSatisfies(err -> {
assertThat(err).isInstanceOf(ResponseStatusException.class); assertThat(err).isInstanceOf(ResponseStatusException.class);
assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404);
}).verify(TIMEOUT); }).verify(TIMEOUT);
// SPR-17475 // SPR-17475

View File

@ -133,9 +133,7 @@ public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionRes
ex.getResponseHeaders().forEach((name, values) -> ex.getResponseHeaders().forEach((name, values) ->
values.forEach(value -> response.addHeader(name, value))); values.forEach(value -> response.addHeader(name, value)));
int statusCode = ex.getStatus().value(); return applyStatusAndReason(ex.getRawStatusCode(), ex.getReason(), response);
String reason = ex.getReason();
return applyStatusAndReason(statusCode, reason, response);
} }
/** /**