From 2109559f37095fd2fcfec4868db8d52fc0c92e59 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 20 Oct 2015 13:53:54 +0100 Subject: [PATCH] Ensure that, where appropriate, actuator endpoints always produce JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the Actuator’s endpoints did not specify a produces attribute on their request mappings. With Jackson’s XML binding on the classpath, this would lead to requests made by a browser receiving application/xml responses (due to the Accept header indicating that application/xml is preferred). This was problematic as some of the response payloads were not legal xml. Problems included XML tags beginning with ‘\’ or containing ‘#’. This commit updates the endpoints to specify that they produce application/json. The environment and metrics endpoints have also been updated so that always return a JSON object, even when they are returning a single entry. This consistency avoids problems where clients may not consider a single scalar value to be legal JSON. Closes gh-2449 --- .../boot/actuate/endpoint/mvc/EndpointMvcAdapter.java | 3 ++- .../actuate/endpoint/mvc/EnvironmentMvcEndpoint.java | 3 ++- .../boot/actuate/endpoint/mvc/HealthMvcEndpoint.java | 3 ++- .../boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java | 3 ++- .../boot/actuate/endpoint/mvc/NamePatternFilter.java | 9 +++++++-- .../endpoint/mvc/EnvironmentMvcEndpointTests.java | 3 +-- .../actuate/endpoint/mvc/MetricsMvcEndpointTests.java | 2 +- .../actuate/endpoint/mvc/NamePatternFilterTests.java | 8 +++++--- 8 files changed, 22 insertions(+), 12 deletions(-) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointMvcAdapter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointMvcAdapter.java index 13436493519..b3b4a7a199f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointMvcAdapter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointMvcAdapter.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.endpoint.mvc; import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; @@ -38,7 +39,7 @@ public class EndpointMvcAdapter extends AbstractEndpointMvcAdapter> } @Override - @RequestMapping(method = RequestMethod.GET) + @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Object invoke() { return super.invoke(); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java index 49dbfa2da28..d28231aabfa 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java @@ -24,6 +24,7 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -46,7 +47,7 @@ public class EnvironmentMvcEndpoint extends EndpointMvcAdapter super(delegate); } - @RequestMapping(value = "/{name:.*}", method = RequestMethod.GET) + @RequestMapping(value = "/{name:.*}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @HypermediaDisabled public Object value(@PathVariable String name) { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpoint.java index 819817fd23e..51dc346060e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpoint.java @@ -28,6 +28,7 @@ import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -123,7 +124,7 @@ public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter The source data type * @author Phillip Webb * @author Sergei Egorov + * @author Andy Wilkinson * @since 1.3.0 */ abstract class NamePatternFilter { @@ -41,9 +43,12 @@ abstract class NamePatternFilter { this.source = source; } - public Object getResults(String name) { + public Map getResults(String name) { if (!isRegex(name)) { - return getValue(this.source, name); + Object value = getValue(this.source, name); + Map result = new HashMap(); + result.put(name, value); + return result; } Pattern pattern = Pattern.compile(name); ResultCollectingNameCallback resultCollector = new ResultCollectingNameCallback( diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java index 3072ca83906..2e01d179128 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java @@ -41,7 +41,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -79,7 +78,7 @@ public class EnvironmentMvcEndpointTests { @Test public void sub() throws Exception { this.mvc.perform(get("/env/foo")).andExpect(status().isOk()) - .andExpect(content().string(equalToIgnoringCase("bar"))); + .andExpect(content().string("{\"foo\":\"bar\"}")); } @Test diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java index e392bce3951..ac48dc14a4a 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java @@ -87,7 +87,7 @@ public class MetricsMvcEndpointTests { @Test public void specificMetric() throws Exception { this.mvc.perform(get("/metrics/foo")).andExpect(status().isOk()) - .andExpect(content().string(equalTo("1"))); + .andExpect(content().string(equalTo("{\"foo\":1}"))); } @Test diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilterTests.java index 0a52843d434..bdc0a006e69 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilterTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilterTests.java @@ -21,27 +21,29 @@ import java.util.Map; import org.junit.Test; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; import static org.junit.Assert.assertThat; /** * Tests for {@link NamePatternFilter}. * * @author Phillip Webb + * @author Andy Wilkinson */ public class NamePatternFilterTests { @Test public void nonRegex() throws Exception { MockNamePatternFilter filter = new MockNamePatternFilter(); - assertThat(filter.getResults("not.a.regex"), equalTo((Object) "not.a.regex")); + assertThat(filter.getResults("not.a.regex"), + hasEntry("not.a.regex", (Object) "not.a.regex")); assertThat(filter.isGetNamesCalled(), equalTo(false)); } @Test - @SuppressWarnings("unchecked") public void regex() throws Exception { MockNamePatternFilter filter = new MockNamePatternFilter(); - Map results = (Map) filter.getResults("fo.*"); + Map results = filter.getResults("fo.*"); assertThat(results.get("foo"), equalTo((Object) "foo")); assertThat(results.get("fool"), equalTo((Object) "fool")); assertThat(filter.isGetNamesCalled(), equalTo(true));