From d944145ce300e98ebe63987ad6d50a7d316a13df Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 5 Nov 2014 16:58:34 +0000 Subject: [PATCH] Make hystrix dashboard work with a context path I didn't see a way to do this without dynamic rendering, so now we have a freemarker dependency (not huge I guess). Fixes gh-51 --- .../archaius/ArchaiusAutoConfiguration.java | 3 + .../eureka/EurekaClientConfiguration.java | 1 - .../pom.xml | 13 ++- .../HystrixDashboardConfiguration.java | 5 ++ .../dashboard/HystrixDashboardController.java | 52 +++++++++++ .../hystrixCommand/hystrixCommand.js | 4 +- .../hystrixThreadPool/hystrixThreadPool.js | 4 +- .../hystrix/{monitor => css}/monitor.css | 0 .../index.html => templates/index.ftl} | 5 +- .../monitor.html => templates/monitor.ftl} | 25 +++--- .../HystrixDashboardContextTests.java | 86 +++++++++++++++++++ .../dashboard/HystrixDashboardTests.java | 79 +++++++++++++++++ 12 files changed, 254 insertions(+), 23 deletions(-) create mode 100644 spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardController.java rename spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/{monitor => css}/monitor.css (100%) rename spring-cloud-netflix-hystrix-dashboard/src/main/resources/{static/hystrix/index.html => templates/index.ftl} (89%) rename spring-cloud-netflix-hystrix-dashboard/src/main/resources/{static/hystrix/monitor/monitor.html => templates/monitor.ftl} (86%) create mode 100644 spring-cloud-netflix-hystrix-dashboard/src/test/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardContextTests.java create mode 100644 spring-cloud-netflix-hystrix-dashboard/src/test/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardTests.java diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/archaius/ArchaiusAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/archaius/ArchaiusAutoConfiguration.java index da7c1ec7..5c83b2fa 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/archaius/ArchaiusAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/archaius/ArchaiusAutoConfiguration.java @@ -10,11 +10,13 @@ import static com.netflix.config.ConfigurationManager.URL_CONFIG_NAME; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.commons.configuration.ConfigurationBuilder; import org.apache.commons.configuration.EnvironmentConfiguration; import org.apache.commons.configuration.SystemConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; @@ -27,6 +29,7 @@ import com.netflix.config.DynamicURLConfiguration; * @author Spencer Gibb */ @Configuration +@ConditionalOnClass({ConcurrentCompositeConfiguration.class, ConfigurationBuilder.class}) public class ArchaiusAutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(ArchaiusAutoConfiguration.class); diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfiguration.java index 85acb3dd..2f0f850a 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfiguration.java @@ -26,7 +26,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.context.ApplicationListener; import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.Bean; diff --git a/spring-cloud-netflix-hystrix-dashboard/pom.xml b/spring-cloud-netflix-hystrix-dashboard/pom.xml index 33284543..fa990f66 100644 --- a/spring-cloud-netflix-hystrix-dashboard/pom.xml +++ b/spring-cloud-netflix-hystrix-dashboard/pom.xml @@ -20,6 +20,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-freemarker + org.springframework.cloud spring-cloud-netflix-core @@ -28,10 +32,6 @@ org.apache.httpcomponents httpclient - - com.netflix.eureka - eureka-client - com.netflix.hystrix hystrix-core @@ -48,5 +48,10 @@ org.webjars d3js + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java b/spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java index a584dc14..3da958e3 100644 --- a/spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java +++ b/spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java @@ -37,6 +37,11 @@ public class HystrixDashboardConfiguration { return new ServletRegistrationBean(new ProxyStreamServlet(), "/proxy.stream"); } + @Bean + public HystrixDashboardController hsytrixDashboardController() { + return new HystrixDashboardController(); + } + /** * Proxy an EventStream request (data.stream via proxy.stream) since EventStream does not yet support CORS (https://bugs.webkit.org/show_bug.cgi?id=61862) * so that a UI can request a stream from a different server. diff --git a/spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardController.java b/spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardController.java new file mode 100644 index 00000000..a562c35e --- /dev/null +++ b/spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardController.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-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 org.springframework.cloud.netflix.hystrix.dashboard; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.WebRequest; + +/** + * @author Dave Syer + * + */ +@Controller +public class HystrixDashboardController { + + @RequestMapping("/hystrix") + public String home(Model model, WebRequest request) { + model.addAttribute("basePath", extractPath(request)); + return "index"; + } + + @RequestMapping("/hystrix/monitor") + public String monitor(Model model, WebRequest request) { + model.addAttribute("basePath", extractPath(request)); + model.addAttribute("contextPath", request.getContextPath()); + return "monitor"; + } + + private String extractPath(WebRequest request) { + String path = request.getContextPath() + + (String) request + .getAttribute( + "org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping", + WebRequest.SCOPE_REQUEST); + return path; + } + +} diff --git a/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.js b/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.js index dc375759..01fa98f0 100644 --- a/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.js +++ b/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.js @@ -2,10 +2,10 @@ (function(window) { // cache the templates we use on this page as global variables (asynchronously) - jQuery.get(getRelativePath("../components/hystrixCommand/templates/hystrixCircuit.html"), function(data) { + jQuery.get(getRelativePath("components/hystrixCommand/templates/hystrixCircuit.html"), function(data) { hystrixTemplateCircuit = data; }); - jQuery.get(getRelativePath("../components/hystrixCommand/templates/hystrixCircuitContainer.html"), function(data) { + jQuery.get(getRelativePath("components/hystrixCommand/templates/hystrixCircuitContainer.html"), function(data) { hystrixTemplateCircuitContainer = data; }); diff --git a/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.js b/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.js index a5003bc4..9f6a3326 100644 --- a/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.js +++ b/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.js @@ -2,10 +2,10 @@ (function(window) { // cache the templates we use on this page as global variables (asynchronously) - jQuery.get(getRelativePath("../components/hystrixThreadPool/templates/hystrixThreadPool.html"), function(data) { + jQuery.get(getRelativePath("components/hystrixThreadPool/templates/hystrixThreadPool.html"), function(data) { htmlTemplate = data; }); - jQuery.get(getRelativePath("../components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html"), function(data) { + jQuery.get(getRelativePath("components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html"), function(data) { htmlTemplateContainer = data; }); diff --git a/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/monitor/monitor.css b/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/css/monitor.css similarity index 100% rename from spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/monitor/monitor.css rename to spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/css/monitor.css diff --git a/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/index.html b/spring-cloud-netflix-hystrix-dashboard/src/main/resources/templates/index.ftl similarity index 89% rename from spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/index.html rename to spring-cloud-netflix-hystrix-dashboard/src/main/resources/templates/index.ftl index 1c0bc56b..9a20d202 100644 --- a/spring-cloud-netflix-hystrix-dashboard/src/main/resources/static/hystrix/index.html +++ b/spring-cloud-netflix-hystrix-dashboard/src/main/resources/templates/index.ftl @@ -1,17 +1,18 @@ + Hystrix Dashboard - + + - - - + + + - - + + - - + + @@ -96,8 +97,8 @@ stream = stream + "&delay=" + getUrlVars()["delay"]; } - var commandStream = "/proxy.stream?origin=" + stream; - var poolStream = "/proxy.stream?origin=" + stream; + var commandStream = "${contextPath}/proxy.stream?origin=" + stream; + var poolStream = "${contextPath}/proxy.stream?origin=" + stream; if(getUrlVars()["title"] != undefined) { $('#title_name').html("Hystrix Stream: " + decodeURIComponent(getUrlVars()["title"])) diff --git a/spring-cloud-netflix-hystrix-dashboard/src/test/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardContextTests.java b/spring-cloud-netflix-hystrix-dashboard/src/test/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardContextTests.java new file mode 100644 index 00000000..fe7c529b --- /dev/null +++ b/spring-cloud-netflix-hystrix-dashboard/src/test/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardContextTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2013-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 org.springframework.cloud.netflix.hystrix.dashboard; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.cloud.netflix.hystrix.dashboard.HystrixDashboardContextTests.Application; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +/** + * @author Dave Syer + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Application.class) +@WebAppConfiguration +@IntegrationTest({ "server.port=0", "spring.application.name=hystrix-dashboard", "server.contextPath=/context" }) +public class HystrixDashboardContextTests { + + @Value("${local.server.port}") + private int port = 0; + + @Test + public void homePage() { + ResponseEntity entity = new TestRestTemplate().getForEntity( + "http://localhost:" + port + "/context/hystrix", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + } + + @Test + public void cssAvailable() { + ResponseEntity entity = new TestRestTemplate().getForEntity( + "http://localhost:" + port + "/context/hystrix/css/global.css", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + } + + @Test + public void webjarsAvailable() { + ResponseEntity entity = new TestRestTemplate().getForEntity( + "http://localhost:" + port + "/context/webjars/jquery/2.1.1/jquery.min.js", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + } + + @Test + public void monitorPage() { + ResponseEntity entity = new TestRestTemplate().getForEntity( + "http://localhost:" + port + "/context/hystrix/monitor", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + } + + @Configuration + @EnableAutoConfiguration + @EnableHystrixDashboard + protected static class Application { + public static void main(String[] args) { + new SpringApplicationBuilder(Application.class).properties( + "spring.application.name=hystrix-dashboard", "server.contextPath=/context").run(); + } + } + +} diff --git a/spring-cloud-netflix-hystrix-dashboard/src/test/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardTests.java b/spring-cloud-netflix-hystrix-dashboard/src/test/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardTests.java new file mode 100644 index 00000000..40721457 --- /dev/null +++ b/spring-cloud-netflix-hystrix-dashboard/src/test/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013-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 org.springframework.cloud.netflix.hystrix.dashboard; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.cloud.netflix.hystrix.dashboard.HystrixDashboardTests.Application; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +/** + * @author Dave Syer + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Application.class) +@WebAppConfiguration +@IntegrationTest({ "server.port=0", "spring.application.name=hystrix-dashboard" }) +public class HystrixDashboardTests { + + @Value("${local.server.port}") + private int port = 0; + + @Test + public void homePage() { + ResponseEntity entity = new TestRestTemplate().getForEntity( + "http://localhost:" + port + "/hystrix", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + } + + @Test + public void cssAvailable() { + ResponseEntity entity = new TestRestTemplate().getForEntity( + "http://localhost:" + port + "/hystrix/css/global.css", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + } + + @Test + public void monitorPage() { + ResponseEntity entity = new TestRestTemplate().getForEntity( + "http://localhost:" + port + "/hystrix/monitor", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + } + + @Configuration + @EnableAutoConfiguration + @EnableHystrixDashboard + protected static class Application { + public static void main(String[] args) { + new SpringApplicationBuilder(Application.class).properties( + "spring.application.name=hystrix-dashboard").run(); + } + } + +}