Skip to content

Commit

Permalink
Add context-path property for admin server
Browse files Browse the repository at this point in the history
Cause it was often requested, now the context-path for the admin server is
configurable. This allows to move the admin-ui and endpoints to other http-
paths than "/".
NOTE: I'd still advise not to add the admin to the application you want to
monitor.
  • Loading branch information
joshiste committed Dec 13, 2015
1 parent 0f759da commit e50ad9d
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 19 deletions.
11 changes: 8 additions & 3 deletions spring-boot-admin-docs/src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,16 @@ spring.boot.admin.password
|===
| Property name |Description |Default value

=======
| spring.boot.admin.context-path
| The context-path prefixes the path where the Admin Servers statics assets and api should be served. Relative to the Dispatcher-Servlet.
|
| spring.boot.admin.monitor.period
| Time interval in ms to update the status of applications with expired status-informations.
| 10.000
| spring.boot.admin.status-lifetime
| spring.boot.admin.monitor.status-lifetime
| Lifetime of iapplication statuses in ms. The applications /health-endpoint will not be queried until the lifetime has expired.
| 10.000
|===
Expand Down Expand Up @@ -416,8 +421,8 @@ To enable pagerduty notifications you just have to add a generic service to your
== FAQs ==
[qanda]
Can I include spring-boot-admin into my business application?::
*tl;dr* you shouldn't. +
In general it is possible but be aware that the admin-ui is serverd on `/` so most likely you will get conflicts here. On the other hand in my opinion it makes no sense for an application to monitor itself. In case your application goes down your monitioring tool also does.
*tl;dr* You can, but you shouldn't. +
You can set `spring.boot.admin.context-path` to alter the path where the UI and REST-api is served, but depending on the complexity of your application you might get in trouble. On the other hand in my opinion it makes no sense for an application to monitor itself. In case your application goes down your monitioring tool also does.
How do I disable Spring Boot Admin Client for my unit tests?::
The AutoConfiguration is triggered by the presence of the `spring.boot.admin.url`. So if you don't set this property for your tests, the Spring Boot Admin Client is not active.
Expand Down
2 changes: 1 addition & 1 deletion spring-boot-admin-server-ui/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<resources>
<resource>
<directory>target/dist</directory>
<targetPath>META-INF/resources</targetPath>
<targetPath>META-INF/spring-boot-admin-server-ui</targetPath>
</resource>
</resources>
</build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,26 @@
@ConfigurationProperties("spring.boot.admin")
public class AdminServerProperties {

/**
* The context-path prefixes the path where the Admin Servers statics assets and api should be
* served. Relative to the Dispatcher-Servlet.
*/
private String contextPath = "";

private MonitorProperties monitor = new MonitorProperties();

public void setContextPath(String pathPrefix) {
if (!pathPrefix.startsWith("/") || pathPrefix.endsWith("/")) {
throw new IllegalArgumentException(
"ContextPath must start with '/' and not end with '/'");
}
this.contextPath = pathPrefix;
}

public String getContextPath() {
return contextPath;
}

public MonitorProperties getMonitor() {
return monitor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
Expand All @@ -31,8 +32,11 @@
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.util.StringUtils;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -51,6 +55,7 @@
import de.codecentric.boot.admin.registry.StatusUpdater;
import de.codecentric.boot.admin.registry.store.ApplicationStore;
import de.codecentric.boot.admin.registry.store.SimpleApplicationStore;
import de.codecentric.boot.admin.web.PrefixHandlerMapping;

@Configuration
@EnableConfigurationProperties
Expand All @@ -65,6 +70,9 @@ public class AdminServerWebConfiguration extends WebMvcConfigurerAdapter
@Autowired
private ApplicationStore applicationStore;

@Autowired
private ServerProperties server;

@Bean
@ConditionalOnMissingBean
public AdminServerProperties adminServerProperties() {
Expand Down Expand Up @@ -95,6 +103,30 @@ private boolean hasConverter(List<HttpMessageConverter<?>> converters,
return false;
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(adminServerProperties().getContextPath() + "/**")
.addResourceLocations("classpath:/META-INF/spring-boot-admin-server-ui/");
}

@Override
public void addViewControllers(ViewControllerRegistry registry) {
if (StringUtils.hasText(adminServerProperties().getContextPath())) {
registry.addRedirectViewController(adminServerProperties().getContextPath(),
server.getPath(adminServerProperties().getContextPath()) + "/");
}
registry.addViewController(adminServerProperties().getContextPath() + "/")
.setViewName("forward:index.html");
}

@Bean
public PrefixHandlerMapping prefixHandlerMapping() {
PrefixHandlerMapping prefixHandlerMapping = new PrefixHandlerMapping(registryController(),
journalController());
prefixHandlerMapping.setPrefix(adminServerProperties().getContextPath());
return prefixHandlerMapping;
}

/**
* @return Controller with REST-API for spring-boot applications to register itself.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.cloud.netflix.zuul.ZuulConfiguration;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
Expand All @@ -28,13 +29,13 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import de.codecentric.boot.admin.controller.RegistryController;
import de.codecentric.boot.admin.event.RoutesOutdatedEvent;
import de.codecentric.boot.admin.registry.ApplicationRegistry;
import de.codecentric.boot.admin.zuul.ApplicationRouteLocator;
import de.codecentric.boot.admin.zuul.PreDecorationFilter;

@Configuration
@AutoConfigureAfter({ AdminServerWebConfiguration.class })
public class RevereseZuulProxyConfiguration extends ZuulConfiguration {

@Autowired(required = false)
Expand All @@ -46,11 +47,14 @@ public class RevereseZuulProxyConfiguration extends ZuulConfiguration {
@Autowired
private ApplicationRegistry registry;

@Autowired
private AdminServerProperties adminServer;

@Bean
@Override
public ApplicationRouteLocator routeLocator() {
return new ApplicationRouteLocator(this.server.getServletPrefix(), registry,
RegistryController.PATH);
adminServer.getContextPath() + "/api/applications");
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,17 @@
import java.util.Collection;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.ResponseBody;

import de.codecentric.boot.admin.event.ClientApplicationEvent;
import de.codecentric.boot.admin.journal.ApplicationEventJournal;

/**
* REST-Controller for querying all client application events.
*
* @author Johannes Stelzer
* @author Johannes Edmeier
*/
@RestController
@RequestMapping("/api/journal")
@ResponseBody
public class JournalController {

private ApplicationEventJournal eventJournal;
Expand All @@ -38,7 +37,7 @@ public JournalController(ApplicationEventJournal eventJournal) {
this.eventJournal = eventJournal;
}

@RequestMapping
@RequestMapping("/api/journal")
public Collection<ClientApplicationEvent> getJournal() {
return eventJournal.getEvents();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,16 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.ResponseBody;

import de.codecentric.boot.admin.model.Application;
import de.codecentric.boot.admin.registry.ApplicationRegistry;

/**
* REST controller for controlling registration of managed applications.
*/
@RestController
@RequestMapping(value = RegistryController.PATH)
@ResponseBody
public class RegistryController {
public static final String PATH = "/api/applications";

private static final Logger LOGGER = LoggerFactory.getLogger(RegistryController.class);

Expand All @@ -53,7 +51,7 @@ public RegistryController(ApplicationRegistry registry) {
* @param app The application infos.
* @return The registered application.
*/
@RequestMapping(method = RequestMethod.POST)
@RequestMapping(value = "/api/applications", method = RequestMethod.POST)
public ResponseEntity<Application> register(@RequestBody Application app) {
LOGGER.debug("Register application {}", app.toString());
Application registeredApp = registry.register(app);
Expand All @@ -66,7 +64,7 @@ public ResponseEntity<Application> register(@RequestBody Application app) {
* @param name the name to search for
* @return List
*/
@RequestMapping(method = RequestMethod.GET)
@RequestMapping(value = "/api/applications", method = RequestMethod.GET)
public Collection<Application> applications(
@RequestParam(value = "name", required = false) String name) {
LOGGER.debug("Deliver registered applications with name={}", name);
Expand All @@ -83,7 +81,7 @@ public Collection<Application> applications(
* @param id The application identifier.
* @return The registered application.
*/
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@RequestMapping(value = "/api/applications/{id}", method = RequestMethod.GET)
public ResponseEntity<?> get(@PathVariable String id) {
LOGGER.debug("Deliver registered application with ID '{}'", id);
Application application = registry.getApplication(id);
Expand All @@ -100,7 +98,7 @@ public ResponseEntity<?> get(@PathVariable String id) {
* @param id The application id.
* @return the unregistered application.
*/
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@RequestMapping(value = "/api/applications/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?> unregister(@PathVariable String id) {
LOGGER.debug("Unregister application with ID '{}'", id);
Application application = registry.deregister(id);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.codecentric.boot.admin.web;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/**
* {@link HandlerMapping} to map {@code @RequestMapping} on objects and prefixes them. The semantics
* of {@code @RequestMapping} should be identical to a normal {@code @Controller}, but the Objects
* should not be annotated as {@code @Controller} (otherwise they will be mapped by the normal MVC
* mechanisms).
*
* @author Johannes Edmeier
*/
public class PrefixHandlerMapping extends RequestMappingHandlerMapping {
private String prefix = "";
private final Object handlers[];

public PrefixHandlerMapping(Object... handlers) {
this.handlers = handlers.clone();
setOrder(-50);
}

@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
for (Object handler : handlers) {
detectHandlerMethods(handler);
}
}

@Override
protected boolean isHandler(Class<?> beanType) {
return false;
}

@Override
protected void registerHandlerMethod(Object handler, Method method,
RequestMappingInfo mapping) {
if (mapping == null) {
return;
}
super.registerHandlerMethod(handler, method, withPrefix(mapping));
}

private RequestMappingInfo withPrefix(RequestMappingInfo mapping) {
List<String> newPatterns = getPatterns(mapping);

PatternsRequestCondition patterns = new PatternsRequestCondition(
newPatterns.toArray(new String[newPatterns.size()]));
return new RequestMappingInfo(patterns, mapping.getMethodsCondition(),
mapping.getParamsCondition(), mapping.getHeadersCondition(),
mapping.getConsumesCondition(), mapping.getProducesCondition(),
mapping.getCustomCondition());
}

private List<String> getPatterns(RequestMappingInfo mapping) {
List<String> newPatterns = new ArrayList<String>(
mapping.getPatternsCondition().getPatterns().size());
for (String pattern : mapping.getPatternsCondition().getPatterns()) {
newPatterns.add(prefix + pattern);
}
return newPatterns;
}

public void setPrefix(String prefix) {
this.prefix = prefix;
}

public String getPrefix() {
return prefix;
}

}

0 comments on commit e50ad9d

Please sign in to comment.