From 37366e0c911b6b8e7f8b47c3e291404df2e53ff6 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 9 Jul 2020 15:06:33 +0300 Subject: [PATCH] Support for custom status in ResponseStatusException Closes gh-20336 --- .../web/server/ResponseStatusException.java | 36 +++++++++++++++++-- .../ResponseStatusExceptionHandler.java | 32 ++++++++++++----- ...WebFluxResponseStatusExceptionHandler.java | 13 +++---- .../resource/ResourceWebHandlerTests.java | 12 +++---- .../ResponseStatusExceptionResolver.java | 4 +-- 5 files changed, 68 insertions(+), 29 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/server/ResponseStatusException.java b/spring-web/src/main/java/org/springframework/web/server/ResponseStatusException.java index 67c8d78391..8582e8625c 100644 --- a/spring-web/src/main/java/org/springframework/web/server/ResponseStatusException.java +++ b/spring-web/src/main/java/org/springframework/web/server/ResponseStatusException.java @@ -36,7 +36,7 @@ import org.springframework.util.Assert; @SuppressWarnings("serial") public class ResponseStatusException extends NestedRuntimeException { - private final HttpStatus status; + private final int status; @Nullable private final String reason; @@ -70,15 +70,44 @@ public class ResponseStatusException extends NestedRuntimeException { public ResponseStatusException(HttpStatus status, @Nullable String reason, @Nullable Throwable cause) { super(null, cause); 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; } /** * 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() { + 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; } @@ -121,7 +150,8 @@ public class ResponseStatusException extends NestedRuntimeException { @Override 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()); } diff --git a/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java b/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java index cda26cba21..52074e66f9 100644 --- a/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java +++ b/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java @@ -88,9 +88,10 @@ public class ResponseStatusExceptionHandler implements WebExceptionHandler { private boolean updateResponse(ServerHttpResponse response, Throwable ex) { boolean result = false; - HttpStatus status = determineStatus(ex); - if (status != null) { - if (response.setStatusCode(status)) { + HttpStatus httpStatus = determineStatus(ex); + int code = (httpStatus != null ? httpStatus.value() : determineRawStatusCode(ex)); + if (code != -1) { + if (response.setRawStatusCode(code)) { if (ex instanceof ResponseStatusException) { ((ResponseStatusException) ex).getResponseHeaders() .forEach((name, values) -> @@ -109,17 +110,30 @@ public class ResponseStatusExceptionHandler implements WebExceptionHandler { } /** - * Determine the HTTP status implied by the given exception. - * @param ex the exception to introspect + * Determine the HTTP status for the given exception. + *

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 - * @since 5.0.5 + * @deprecated as of 5.3 in favor of {@link #determineRawStatusCode(Throwable)}. */ @Nullable + @Deprecated protected HttpStatus determineStatus(Throwable ex) { - if (ex instanceof ResponseStatusException) { - return ((ResponseStatusException) ex).getStatus(); - } 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; + } + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/WebFluxResponseStatusExceptionHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/WebFluxResponseStatusExceptionHandler.java index 4ecd685d18..efc2015bea 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/WebFluxResponseStatusExceptionHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/WebFluxResponseStatusExceptionHandler.java @@ -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"); * you may not use this file except in compliance with the License. @@ -17,8 +17,6 @@ package org.springframework.web.reactive.handler; 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.server.handler.ResponseStatusExceptionHandler; @@ -39,13 +37,12 @@ import org.springframework.web.server.handler.ResponseStatusExceptionHandler; public class WebFluxResponseStatusExceptionHandler extends ResponseStatusExceptionHandler { @Override - @Nullable - protected HttpStatus determineStatus(Throwable ex) { - HttpStatus status = super.determineStatus(ex); - if (status == null) { + protected int determineRawStatusCode(Throwable ex) { + int status = super.determineRawStatusCode(ex); + if (status == -1) { ResponseStatus ann = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); if (ann != null) { - status = ann.code(); + status = ann.code().value(); } } return status; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java index 1b3f9a2cdf..fc8f292258 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java @@ -275,7 +275,7 @@ public class ResourceWebHandlerTests { StepVerifier.create(handler.handle(exchange)) .expectErrorSatisfies(err -> { assertThat(err).isInstanceOf(ResponseStatusException.class); - assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404); }).verify(TIMEOUT); } @@ -321,7 +321,7 @@ public class ResourceWebHandlerTests { StepVerifier.create(this.handler.handle(exchange)) .expectErrorSatisfies(err -> { assertThat(err).isInstanceOf(ResponseStatusException.class); - assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404); }) .verify(TIMEOUT); if (!location.createRelative(requestPath).exists() && !requestPath.contains(":")) { @@ -416,7 +416,7 @@ public class ResourceWebHandlerTests { StepVerifier.create(this.handler.handle(exchange)) .expectErrorSatisfies(err -> { assertThat(err).isInstanceOf(ResponseStatusException.class); - assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404); }).verify(TIMEOUT); } @@ -427,7 +427,7 @@ public class ResourceWebHandlerTests { StepVerifier.create(this.handler.handle(exchange)) .expectErrorSatisfies(err -> { assertThat(err).isInstanceOf(ResponseStatusException.class); - assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404); }).verify(TIMEOUT); } @@ -438,7 +438,7 @@ public class ResourceWebHandlerTests { StepVerifier.create(this.handler.handle(exchange)) .expectErrorSatisfies(err -> { assertThat(err).isInstanceOf(ResponseStatusException.class); - assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404); }).verify(TIMEOUT); } @@ -473,7 +473,7 @@ public class ResourceWebHandlerTests { StepVerifier.create(mono) .expectErrorSatisfies(err -> { assertThat(err).isInstanceOf(ResponseStatusException.class); - assertThat(((ResponseStatusException) err).getStatus()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(((ResponseStatusException) err).getRawStatusCode()).isEqualTo(404); }).verify(TIMEOUT); // SPR-17475 diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java index 63873d01ff..6fd143c76d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java @@ -133,9 +133,7 @@ public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionRes ex.getResponseHeaders().forEach((name, values) -> values.forEach(value -> response.addHeader(name, value))); - int statusCode = ex.getStatus().value(); - String reason = ex.getReason(); - return applyStatusAndReason(statusCode, reason, response); + return applyStatusAndReason(ex.getRawStatusCode(), ex.getReason(), response); } /**