Add a shim Endpoint if management context is child
When management endpoints are on a different port the HandlerMappings are restricted to a single EndpointHandlerMapping, so the error controller (which is a normal @Controller with @RequestMappings) does not get mapped. Fixed by addinga shim Endpoint on "/error" that delegates to the ErrorController (which interface picks up an extra method).
This commit is contained in:
parent
25d9ac6535
commit
bcae284dd9
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
|
|
@ -23,18 +25,25 @@ import org.springframework.beans.factory.BeanFactoryUtils;
|
|||
import org.springframework.beans.factory.HierarchicalBeanFactory;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerAdapter;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
|
||||
import org.springframework.boot.actuate.properties.ManagementServerProperties;
|
||||
import org.springframework.boot.actuate.web.ErrorController;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
|
||||
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainerFactory;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
|
||||
import org.springframework.boot.context.embedded.ErrorPage;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.HandlerAdapter;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
|
@ -52,6 +61,9 @@ public class EndpointWebMvcChildContextConfiguration {
|
|||
protected static class ServerCustomization implements
|
||||
EmbeddedServletContainerCustomizer {
|
||||
|
||||
@Value("${error.path:/error}")
|
||||
private String errorPath = "/error";
|
||||
|
||||
@Autowired
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
|
|
@ -69,6 +81,7 @@ public class EndpointWebMvcChildContextConfiguration {
|
|||
factory.setPort(this.managementServerProperties.getPort());
|
||||
factory.setAddress(this.managementServerProperties.getAddress());
|
||||
factory.setContextPath(this.managementServerProperties.getContextPath());
|
||||
factory.addErrorPages(new ErrorPage(this.errorPath));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -96,6 +109,24 @@ public class EndpointWebMvcChildContextConfiguration {
|
|||
return new EndpointHandlerAdapter();
|
||||
}
|
||||
|
||||
/*
|
||||
* The error controller is present but not mapped as an endpoint in this context
|
||||
* because of the DispatcherServlet having had it's HandlerMapping explicitly
|
||||
* disabled. So this tiny shim exposes the same feature but only for machine
|
||||
* endpoints.
|
||||
*/
|
||||
@Bean
|
||||
public Endpoint<Map<String, Object>> errorEndpoint(final ErrorController controller) {
|
||||
return new AbstractEndpoint<Map<String, Object>>("/error", false, true) {
|
||||
@Override
|
||||
protected Map<String, Object> doInvoke() {
|
||||
RequestAttributes attributes = RequestContextHolder
|
||||
.currentRequestAttributes();
|
||||
return controller.extract(attributes, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass({ EnableWebSecurity.class, Filter.class })
|
||||
@ConditionalOnBean(name = "springSecurityFilterChain", search = SearchStrategy.PARENTS)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration.DefaultTemplateResolverConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
|
||||
|
|
@ -72,7 +73,7 @@ public class ErrorMvcAutoConfiguration implements EmbeddedServletContainerCustom
|
|||
private String errorPath = "/error";
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ErrorController.class)
|
||||
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
|
||||
public BasicErrorController basicErrorController() {
|
||||
return new BasicErrorController();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
|
|
@ -61,19 +63,27 @@ public class BasicErrorController implements ErrorController {
|
|||
|
||||
@RequestMapping(value = "${error.path:/error}", produces = "text/html")
|
||||
public ModelAndView errorHtml(HttpServletRequest request) {
|
||||
Map<String, Object> map = error(request);
|
||||
Map<String, Object> map = extract(new ServletRequestAttributes(request), false);
|
||||
return new ModelAndView(ERROR_KEY, map);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "${error.path:/error}")
|
||||
@ResponseBody
|
||||
public Map<String, Object> error(HttpServletRequest request) {
|
||||
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
|
||||
String trace = request.getParameter("trace");
|
||||
return extract(attributes, trace != null && !"false".equals(trace.toLowerCase()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> extract(RequestAttributes attributes, boolean trace) {
|
||||
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
map.put("timestamp", new Date());
|
||||
try {
|
||||
Throwable error = (Throwable) request
|
||||
.getAttribute("javax.servlet.error.exception");
|
||||
Object obj = request.getAttribute("javax.servlet.error.status_code");
|
||||
Throwable error = (Throwable) attributes.getAttribute(
|
||||
"javax.servlet.error.exception", RequestAttributes.SCOPE_REQUEST);
|
||||
Object obj = attributes.getAttribute("javax.servlet.error.status_code",
|
||||
RequestAttributes.SCOPE_REQUEST);
|
||||
int status = 999;
|
||||
if (obj != null) {
|
||||
status = (Integer) obj;
|
||||
|
|
@ -89,8 +99,7 @@ public class BasicErrorController implements ErrorController {
|
|||
}
|
||||
map.put("exception", error.getClass().getName());
|
||||
map.put("message", error.getMessage());
|
||||
String trace = request.getParameter("trace");
|
||||
if (trace != null && !"false".equals(trace.toLowerCase())) {
|
||||
if (trace) {
|
||||
StringWriter stackTrace = new StringWriter();
|
||||
error.printStackTrace(new PrintWriter(stackTrace));
|
||||
stackTrace.flush();
|
||||
|
|
@ -99,7 +108,8 @@ public class BasicErrorController implements ErrorController {
|
|||
this.logger.error(error);
|
||||
}
|
||||
else {
|
||||
Object message = request.getAttribute("javax.servlet.error.message");
|
||||
Object message = attributes.getAttribute("javax.servlet.error.message",
|
||||
RequestAttributes.SCOPE_REQUEST);
|
||||
map.put("message", message == null ? "No message available" : message);
|
||||
}
|
||||
return map;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@
|
|||
|
||||
package org.springframework.boot.actuate.web;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
|
||||
/**
|
||||
* Marker interface used to indicate that a {@link Controller @Controller} is used to
|
||||
|
|
@ -31,4 +34,13 @@ public interface ErrorController {
|
|||
*/
|
||||
public String getErrorPath();
|
||||
|
||||
/**
|
||||
* Extract a useful model of the error from the request attributes.
|
||||
*
|
||||
* @param attributes the request attributes
|
||||
* @param trace flag to indicate that stack trace information should be included
|
||||
* @return a model containing error messages and codes etc.
|
||||
*/
|
||||
public Map<String, Object> extract(RequestAttributes attributes, boolean trace);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.boot.sample.ops.ui;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
|
@ -25,7 +27,6 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
|
@ -35,8 +36,6 @@ import org.springframework.http.client.ClientHttpResponse;
|
|||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Integration tests for separate management and main service ports.
|
||||
*
|
||||
|
|
@ -80,9 +79,7 @@ public class SampleActuatorUiApplicationPortTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testMetrics() throws Exception {
|
||||
// FIXME broken since error page is not rendered
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = getRestTemplate().getForEntity(
|
||||
"http://localhost:" + managementPort + "/metrics", Map.class);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.boot.sample.ops;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
|
@ -27,7 +29,6 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityProperties;
|
||||
|
|
@ -44,8 +45,6 @@ import org.springframework.security.crypto.codec.Base64;
|
|||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Integration tests for separate management and main service ports.
|
||||
*
|
||||
|
|
@ -93,9 +92,7 @@ public class ManagementAddressSampleActuatorApplicationTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testMetrics() throws Exception {
|
||||
// FIXME broken because error page is no longer exposed on management port
|
||||
testHome(); // makes sure some requests have been made
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = getRestTemplate().getForEntity(
|
||||
|
|
@ -104,9 +101,7 @@ public class ManagementAddressSampleActuatorApplicationTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testHealth() throws Exception {
|
||||
// FIXME broken because error page is no longer exposed on management port
|
||||
ResponseEntity<String> entity = getRestTemplate().getForEntity(
|
||||
"http://localhost:" + managementPort + "/health", String.class);
|
||||
assertEquals(HttpStatus.OK, entity.getStatusCode());
|
||||
|
|
@ -114,9 +109,7 @@ public class ManagementAddressSampleActuatorApplicationTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testErrorPage() throws Exception {
|
||||
// FIXME broken because error page is no longer exposed on management port
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = getRestTemplate().getForEntity(
|
||||
"http://localhost:" + managementPort + "/error", Map.class);
|
||||
|
|
|
|||
Loading…
Reference in New Issue