Closes gh-21428
This commit is contained in:
commit
0d00947735
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
|
@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure.web.servlet;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -36,6 +37,7 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
|
|||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class CompositeHandlerExceptionResolver implements HandlerExceptionResolver {
|
||||
|
||||
|
@ -50,8 +52,15 @@ class CompositeHandlerExceptionResolver implements HandlerExceptionResolver {
|
|||
if (this.resolvers == null) {
|
||||
this.resolvers = extractResolvers();
|
||||
}
|
||||
return this.resolvers.stream().map((resolver) -> resolver.resolveException(request, response, handler, ex))
|
||||
.filter(Objects::nonNull).findFirst().orElse(null);
|
||||
Optional<ModelAndView> modelAndView = this.resolvers.stream()
|
||||
.map((resolver) -> resolver.resolveException(request, response, handler, ex)).filter(Objects::nonNull)
|
||||
.findFirst();
|
||||
modelAndView.ifPresent((mav) -> {
|
||||
if (mav.isEmpty()) {
|
||||
request.setAttribute("javax.servlet.error.exception", ex);
|
||||
}
|
||||
});
|
||||
return modelAndView.orElse(null);
|
||||
}
|
||||
|
||||
private List<HandlerExceptionResolver> extractResolvers() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
|
@ -38,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
* Tests for {@link CompositeHandlerExceptionResolver}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class CompositeHandlerExceptionResolverTests {
|
||||
|
||||
|
@ -62,9 +63,11 @@ class CompositeHandlerExceptionResolverTests {
|
|||
load(BaseConfiguration.class);
|
||||
CompositeHandlerExceptionResolver resolver = (CompositeHandlerExceptionResolver) this.context
|
||||
.getBean(DispatcherServlet.HANDLER_EXCEPTION_RESOLVER_BEAN_NAME);
|
||||
ModelAndView resolved = resolver.resolveException(this.request, this.response, null,
|
||||
new HttpRequestMethodNotSupportedException("POST"));
|
||||
HttpRequestMethodNotSupportedException exception = new HttpRequestMethodNotSupportedException("POST");
|
||||
ModelAndView resolved = resolver.resolveException(this.request, this.response, null, exception);
|
||||
assertThat(resolved).isNotNull();
|
||||
assertThat(resolved.isEmpty()).isTrue();
|
||||
assertThat(this.request.getAttribute("javax.servlet.error.exception")).isSameAs(exception);
|
||||
}
|
||||
|
||||
private void load(Class<?>... configs) {
|
||||
|
|
|
@ -16,7 +16,12 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.web.servlet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -25,15 +30,21 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAu
|
|||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
|
@ -54,40 +65,80 @@ class WebMvcEndpointChildContextConfigurationIntegrationTests {
|
|||
ServletManagementContextAutoConfiguration.class, WebEndpointAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
|
||||
ErrorMvcAutoConfiguration.class))
|
||||
.withUserConfiguration(FailingEndpoint.class)
|
||||
.withInitializer(new ServerPortInfoApplicationContextInitializer()).withPropertyValues(
|
||||
"server.port=0", "management.server.port=0", "management.endpoints.web.exposure.include=*");
|
||||
.withUserConfiguration(FailingEndpoint.class, FailingControllerEndpoint.class)
|
||||
.withInitializer(new ServerPortInfoApplicationContextInitializer())
|
||||
.withPropertyValues("server.port=0", "management.server.port=0",
|
||||
"management.endpoints.web.exposure.include=*", "server.error.include-exception=true",
|
||||
"server.error.include-message=always", "server.error.include-binding-errors=always");
|
||||
|
||||
@Test // gh-17938
|
||||
@SuppressWarnings("unchecked")
|
||||
void errorPageAndErrorControllerAreUsed() {
|
||||
this.runner.run((context) -> {
|
||||
String port = context.getEnvironment().getProperty("local.management.port");
|
||||
WebClient client = WebClient.create("http://localhost:" + port);
|
||||
void errorEndpointIsUsedWithEndpoint() {
|
||||
this.runner.run(withWebTestClient((client) -> {
|
||||
ClientResponse response = client.get().uri("actuator/fail").accept(MediaType.APPLICATION_JSON).exchange()
|
||||
.block();
|
||||
Map<Object, Object> body = response.bodyToMono(Map.class).block();
|
||||
assertThat(body).containsEntry("message", "");
|
||||
assertThat(body).doesNotContainKey("trace");
|
||||
});
|
||||
Map<String, ?> body = getResponseBody(response);
|
||||
assertThat(body).hasEntrySatisfying("exception",
|
||||
(value) -> assertThat(value).asString().contains("IllegalStateException"));
|
||||
assertThat(body).hasEntrySatisfying("message",
|
||||
(value) -> assertThat(value).asString().contains("Epic Fail"));
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorPageAndErrorControllerIncludeDetails() {
|
||||
this.runner.withPropertyValues("server.error.include-stacktrace=always", "server.error.include-message=always")
|
||||
.run((context) -> {
|
||||
String port = context.getEnvironment().getProperty("local.management.port");
|
||||
WebClient client = WebClient.create("http://localhost:" + port);
|
||||
.run(withWebTestClient((client) -> {
|
||||
ClientResponse response = client.get().uri("actuator/fail").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().block();
|
||||
Map<Object, Object> body = response.bodyToMono(Map.class).block();
|
||||
assertThat(body).containsEntry("message", "Epic Fail");
|
||||
Map<String, ?> body = getResponseBody(response);
|
||||
assertThat(body).hasEntrySatisfying("message",
|
||||
(value) -> assertThat(value).asString().contains("Epic Fail"));
|
||||
assertThat(body).hasEntrySatisfying("trace", (value) -> assertThat(value).asString()
|
||||
.contains("java.lang.IllegalStateException: Epic Fail"));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorEndpointIsUsedWithRestControllerEndpoint() {
|
||||
this.runner.run(withWebTestClient((client) -> {
|
||||
ClientResponse response = client.get().uri("actuator/failController").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().block();
|
||||
Map<String, ?> body = getResponseBody(response);
|
||||
assertThat(body).hasEntrySatisfying("exception",
|
||||
(value) -> assertThat(value).asString().contains("IllegalStateException"));
|
||||
assertThat(body).hasEntrySatisfying("message",
|
||||
(value) -> assertThat(value).asString().contains("Epic Fail"));
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorEndpointIsUsedWithRestControllerEndpointOnBindingError() {
|
||||
this.runner.run(withWebTestClient((client) -> {
|
||||
ClientResponse response = client.post().uri("actuator/failController")
|
||||
.bodyValue(Collections.singletonMap("content", "")).accept(MediaType.APPLICATION_JSON).exchange()
|
||||
.block();
|
||||
Map<String, ?> body = getResponseBody(response);
|
||||
assertThat(body).hasEntrySatisfying("exception",
|
||||
(value) -> assertThat(value).asString().contains("MethodArgumentNotValidException"));
|
||||
assertThat(body).hasEntrySatisfying("message",
|
||||
(value) -> assertThat(value).asString().contains("Validation failed"));
|
||||
assertThat(body).hasEntrySatisfying("errors", (value) -> assertThat(value).asList().isNotEmpty());
|
||||
}));
|
||||
}
|
||||
|
||||
private ContextConsumer<AssertableWebApplicationContext> withWebTestClient(Consumer<WebClient> webClient) {
|
||||
return (context) -> {
|
||||
String port = context.getEnvironment().getProperty("local.management.port");
|
||||
WebClient client = WebClient.create("http://localhost:" + port);
|
||||
webClient.accept(client);
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, ?> getResponseBody(ClientResponse response) {
|
||||
return (Map<String, ?>) response.bodyToMono(Map.class).block();
|
||||
}
|
||||
|
||||
@Component
|
||||
@Endpoint(id = "fail")
|
||||
static class FailingEndpoint {
|
||||
|
||||
|
@ -98,4 +149,35 @@ class WebMvcEndpointChildContextConfigurationIntegrationTests {
|
|||
|
||||
}
|
||||
|
||||
@RestControllerEndpoint(id = "failController")
|
||||
static class FailingControllerEndpoint {
|
||||
|
||||
@GetMapping
|
||||
String fail() {
|
||||
throw new IllegalStateException("Epic Fail");
|
||||
}
|
||||
|
||||
@PostMapping(produces = "application/json")
|
||||
@ResponseBody
|
||||
String bodyValidation(@Valid @RequestBody TestBody body) {
|
||||
return body.getContent();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestBody {
|
||||
|
||||
@NotEmpty
|
||||
private String content;
|
||||
|
||||
public String getContent() {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue