This is an automated email from the ASF dual-hosted git repository.
tomsun28 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/master by this push:
new 375ee5494e [collector]bugfix: honor XML response charset (#4167)
375ee5494e is described below
commit 375ee5494ed32a9c9f95938bbf0c650264b2f32e
Author: hutiefang76 <[email protected]>
AuthorDate: Sat Jul 4 14:27:37 2026 +0800
[collector]bugfix: honor XML response charset (#4167)
Co-authored-by: Tomsun28 <[email protected]>
---
.../collector/collect/http/HttpCollectImpl.java | 39 +++++++++-
.../collect/http/HttpCollectImplTest.java | 90 ++++++++++++++++++++++
2 files changed, 128 insertions(+), 1 deletion(-)
diff --git
a/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/http/HttpCollectImpl.java
b/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/http/HttpCollectImpl.java
index 03e2d0dc4c..ab9f2dca91 100644
---
a/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/http/HttpCollectImpl.java
+++
b/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/http/HttpCollectImpl.java
@@ -30,6 +30,7 @@ import java.io.InterruptedIOException;
import java.io.StringReader;
import java.net.ConnectException;
import java.net.UnknownHostException;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedList;
@@ -82,6 +83,7 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.protocol.HttpContext;
@@ -114,6 +116,10 @@ public class HttpCollectImpl extends AbstractCollect {
*/
private static final List<Pattern> DANGEROUS_XPATH_PATTERNS;
+ private static final Pattern XML_ENCODING_PATTERN = Pattern.compile(
+ "<\\?xml\\s+[^>]*encoding\\s*=\\s*[\"']([^\"']+)[\"']",
+ Pattern.CASE_INSENSITIVE);
+
static {
List<Pattern> patterns = new ArrayList<>();
for (String pattern : CollectorConstants.DANGEROUS_XPATH_PATTERNS) {
@@ -182,7 +188,7 @@ public class HttpCollectImpl extends AbstractCollect {
Option 1: Parse using InputStream, but this requires
significant code changes;
Option 2: Manually trigger garbage collection, similar to
how it's done in Dubbo for large inputs.
*/
- String resp = entity == null ? "" :
EntityUtils.toString(entity, StandardCharsets.UTF_8);
+ String resp = entity == null ? "" :
readEntityAsString(entity, isXmlParseType(parseType));
if (!StringUtils.hasText(resp)) {
log.info("http response entity is empty, status: {}.",
statusCode);
}
@@ -240,6 +246,37 @@ public class HttpCollectImpl extends AbstractCollect {
}
}
+ private boolean isXmlParseType(String parseType) {
+ return DispatchConstants.PARSE_XML_PATH.equals(parseType) ||
DispatchConstants.PARSE_SITE_MAP.equals(parseType);
+ }
+
+ private String readEntityAsString(HttpEntity entity, boolean xmlAware)
throws IOException {
+ if (!xmlAware) {
+ return EntityUtils.toString(entity, StandardCharsets.UTF_8);
+ }
+ byte[] content = EntityUtils.toByteArray(entity);
+ ContentType contentType = ContentType.get(entity);
+ Charset charset = contentType == null ? null :
contentType.getCharset();
+ if (charset == null) {
+ charset = detectXmlCharset(content);
+ }
+ return new String(content, charset);
+ }
+
+ private Charset detectXmlCharset(byte[] content) {
+ int previewLength = Math.min(content.length, 256);
+ String preview = new String(content, 0, previewLength,
StandardCharsets.US_ASCII);
+ Matcher matcher = XML_ENCODING_PATTERN.matcher(preview);
+ if (matcher.find()) {
+ try {
+ return Charset.forName(matcher.group(1));
+ } catch (IllegalArgumentException ignored) {
+ return StandardCharsets.UTF_8;
+ }
+ }
+ return StandardCharsets.UTF_8;
+ }
+
private void parseResponseByHeader(CollectRep.MetricsData.Builder builder,
List<String> aliases, CloseableHttpResponse response) {
CollectRep.ValueRow.Builder valueRowBuilder =
CollectRep.ValueRow.newBuilder();
for (String alias : aliases) {
diff --git
a/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/http/HttpCollectImplTest.java
b/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/http/HttpCollectImplTest.java
index 5ea4596da2..05199a2feb 100644
---
a/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/http/HttpCollectImplTest.java
+++
b/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/http/HttpCollectImplTest.java
@@ -18,6 +18,7 @@
package org.apache.hertzbeat.collector.collect.http;
import com.google.common.collect.Lists;
+import com.sun.net.httpserver.HttpServer;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
@@ -28,6 +29,7 @@ import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -190,6 +192,94 @@ class HttpCollectImplTest {
assertEquals("0", secondRow.getColumns(3), "Second server memory
should be 0");
}
+ @Test
+ void parseResponseByXmlPathHonorsXmlEncodingDeclaration() throws Exception
{
+ String xmlResponse = """
+ <?xml version="1.0" encoding="ISO-8859-1"?>
+ <root>
+ <item>
+ <name>café</name>
+ </item>
+ </root>
+ """;
+ byte[] responseBytes =
xmlResponse.getBytes(StandardCharsets.ISO_8859_1);
+ HttpServer server = HttpServer.create(new
InetSocketAddress("127.0.0.1", 0), 0);
+ server.createContext("/metrics", exchange -> {
+ exchange.getResponseHeaders().add("Content-Type",
"application/xml");
+ exchange.sendResponseHeaders(200, responseBytes.length);
+ exchange.getResponseBody().write(responseBytes);
+ exchange.close();
+ });
+ server.start();
+
+ try {
+ HttpProtocol http = HttpProtocol.builder()
+ .method("GET")
+ .host("127.0.0.1")
+ .port(String.valueOf(server.getAddress().getPort()))
+ .url("/metrics")
+ .parseType(DispatchConstants.PARSE_XML_PATH)
+ .parseScript("//item")
+ .build();
+ Metrics metrics = Metrics.builder()
+ .http(http)
+ .aliasFields(Lists.newArrayList("name"))
+ .build();
+ CollectRep.MetricsData.Builder builder =
CollectRep.MetricsData.newBuilder();
+
+ httpCollectImpl.collect(builder, metrics);
+
+ assertEquals(1, builder.getValuesCount());
+ assertEquals("café", builder.getValues(0).getColumns(0));
+ } finally {
+ server.stop(0);
+ }
+ }
+
+ @Test
+ void parseResponseByXmlPathFallsBackToUtf8ForUnsupportedXmlEncoding()
throws Exception {
+ String xmlResponse = """
+ <?xml version="1.0" encoding="UNKNOWN-CHARSET"?>
+ <root>
+ <item>
+ <name>service</name>
+ </item>
+ </root>
+ """;
+ byte[] responseBytes = xmlResponse.getBytes(StandardCharsets.UTF_8);
+ HttpServer server = HttpServer.create(new
InetSocketAddress("127.0.0.1", 0), 0);
+ server.createContext("/metrics", exchange -> {
+ exchange.getResponseHeaders().add("Content-Type",
"application/xml");
+ exchange.sendResponseHeaders(200, responseBytes.length);
+ exchange.getResponseBody().write(responseBytes);
+ exchange.close();
+ });
+ server.start();
+
+ try {
+ HttpProtocol http = HttpProtocol.builder()
+ .method("GET")
+ .host("127.0.0.1")
+ .port(String.valueOf(server.getAddress().getPort()))
+ .url("/metrics")
+ .parseType(DispatchConstants.PARSE_XML_PATH)
+ .parseScript("//item")
+ .build();
+ Metrics metrics = Metrics.builder()
+ .http(http)
+ .aliasFields(Lists.newArrayList("name"))
+ .build();
+ CollectRep.MetricsData.Builder builder =
CollectRep.MetricsData.newBuilder();
+
+ httpCollectImpl.collect(builder, metrics);
+
+ assertEquals(1, builder.getValuesCount());
+ assertEquals("service", builder.getValues(0).getColumns(0));
+ } finally {
+ server.stop(0);
+ }
+ }
+
@Test
void parseResponseByJsonPath() throws Exception {
String jsonResponse = "{"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]