Skip to content

Commit 4b4dc28

Browse files
committed
Support non-standard error codes with AbstractErrorWebExceptionHandler
Fixes spring-projectsgh-16691
1 parent a695e06 commit 4b4dc28

File tree

3 files changed

+66
-7
lines changed

3 files changed

+66
-7
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
6262
* Currently duplicated from Spring WebFlux HttpWebHandlerAdapter.
6363
*/
6464
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS;
65+
6566
static {
6667
Set<String> exceptions = new HashSet<>();
6768
exceptions.add("AbortedException");
@@ -276,7 +277,8 @@ private void logError(ServerRequest request, ServerResponse response, Throwable
276277
if (logger.isDebugEnabled()) {
277278
logger.debug(request.exchange().getLogPrefix() + formatError(throwable, request));
278279
}
279-
if (response.statusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
280+
if (HttpStatus.resolve(response.rawStatusCode()) != null
281+
&& response.statusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
280282
logger.error(request.exchange().getLogPrefix() + "500 Server Error for " + formatRequest(request),
281283
throwable);
282284
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.autoconfigure.web.reactive.error;
1818

1919
import java.nio.charset.StandardCharsets;
20+
import java.util.ArrayList;
2021
import java.util.Collections;
2122
import java.util.EnumMap;
2223
import java.util.List;
@@ -113,16 +114,26 @@ protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes erro
113114
protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
114115
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML);
115116
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
116-
HttpStatus errorStatus = getHttpStatus(error);
117+
int errorStatus = getHttpStatus(error);
117118
ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);
118-
return Flux
119-
.just("error/" + errorStatus.value(), "error/" + SERIES_VIEWS.get(errorStatus.series()), "error/error")
119+
return Flux.just(getData(errorStatus).toArray(new String[] {}))
120120
.flatMap((viewName) -> renderErrorView(viewName, responseBody, error))
121121
.switchIfEmpty(this.errorProperties.getWhitelabel().isEnabled()
122122
? renderDefaultErrorView(responseBody, error) : Mono.error(getError(request)))
123123
.next();
124124
}
125125

126+
private List<String> getData(int errorStatus) {
127+
HttpStatus errorHttpStatus = HttpStatus.resolve(errorStatus);
128+
List<String> data = new ArrayList<>();
129+
data.add("error/" + errorStatus);
130+
if (errorHttpStatus != null) {
131+
data.add("error/" + SERIES_VIEWS.get(errorHttpStatus.series()));
132+
}
133+
data.add("error/error");
134+
return data;
135+
}
136+
126137
/**
127138
* Render the error information as a JSON payload.
128139
* @param request the current request
@@ -157,9 +168,8 @@ protected boolean isIncludeStackTrace(ServerRequest request, MediaType produces)
157168
* @param errorAttributes the current error information
158169
* @return the error HTTP status
159170
*/
160-
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
161-
int statusCode = (int) errorAttributes.get("status");
162-
return HttpStatus.valueOf(statusCode);
171+
protected int getHttpStatus(Map<String, Object> errorAttributes) {
172+
return (int) errorAttributes.get("status");
163173
}
164174

165175
/**

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerTests.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,37 @@
1616

1717
package org.springframework.boot.autoconfigure.web.reactive.error;
1818

19+
import java.util.Collections;
20+
import java.util.Map;
21+
1922
import org.junit.jupiter.api.Test;
23+
import reactor.core.publisher.Mono;
2024

25+
import org.springframework.boot.autoconfigure.web.ErrorProperties;
26+
import org.springframework.boot.autoconfigure.web.ResourceProperties;
27+
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
28+
import org.springframework.boot.web.reactive.error.ErrorAttributes;
29+
import org.springframework.context.ApplicationContext;
30+
import org.springframework.http.MediaType;
31+
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
32+
import org.springframework.mock.web.server.MockServerWebExchange;
2133
import org.springframework.test.util.ReflectionTestUtils;
34+
import org.springframework.web.reactive.result.view.View;
35+
import org.springframework.web.reactive.result.view.ViewResolver;
36+
import org.springframework.web.server.ServerWebExchange;
2237
import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
2338

2439
import static org.assertj.core.api.Assertions.assertThat;
40+
import static org.mockito.ArgumentMatchers.any;
41+
import static org.mockito.ArgumentMatchers.anyBoolean;
42+
import static org.mockito.BDDMockito.given;
43+
import static org.mockito.Mockito.mock;
2544

2645
/**
2746
* Tests for {@link AbstractErrorWebExceptionHandler}.
2847
*
2948
* @author Phillip Webb
49+
* @author Madhura Bhave
3050
*/
3151
class DefaultErrorWebExceptionHandlerTests {
3252

@@ -39,4 +59,31 @@ void disconnectedClientExceptionsMatchesFramework() {
3959
assertThat(errorHandlers).isNotNull().isEqualTo(webHandlers);
4060
}
4161

62+
@Test
63+
void nonStandardErrorStatusCodeShouldNotFail() {
64+
ErrorAttributes errorAttributes = mock(ErrorAttributes.class);
65+
ResourceProperties resourceProperties = new ResourceProperties();
66+
ErrorProperties errorProperties = new ErrorProperties();
67+
ApplicationContext context = new AnnotationConfigReactiveWebApplicationContext();
68+
given(errorAttributes.getErrorAttributes(any(), anyBoolean())).willReturn(getErrorAttributes());
69+
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
70+
resourceProperties, errorProperties, context);
71+
setupViewResolver(exceptionHandler);
72+
ServerWebExchange exchange = MockServerWebExchange
73+
.from(MockServerHttpRequest.get("/some-other-path").accept(MediaType.TEXT_HTML));
74+
exceptionHandler.handle(exchange, new RuntimeException()).block();
75+
}
76+
77+
private Map<String, Object> getErrorAttributes() {
78+
return Collections.singletonMap("status", 498);
79+
}
80+
81+
private void setupViewResolver(DefaultErrorWebExceptionHandler exceptionHandler) {
82+
View view = mock(View.class);
83+
given(view.render(any(), any(), any())).willReturn(Mono.empty());
84+
ViewResolver viewResolver = mock(ViewResolver.class);
85+
given(viewResolver.resolveViewName(any(), any())).willReturn(Mono.just(view));
86+
exceptionHandler.setViewResolvers(Collections.singletonList(viewResolver));
87+
}
88+
4289
}

0 commit comments

Comments
 (0)