From 1df20676c81c69e6a726b61ff98f5126a913d174 Mon Sep 17 00:00:00 2001 From: Jack Berg <34418638+jack-berg@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:22:31 -0500 Subject: [PATCH] Fix OTLP handling of identity content-encoding --- .../AbstractGrpcTelemetryExporterTest.java | 38 ++++++++++++++++++- .../AbstractHttpTelemetryExporterTest.java | 19 ++++++++++ .../sender/jdk/internal/JdkHttpSender.java | 4 +- .../okhttp/internal/OkHttpHttpSender.java | 4 +- 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java index e855186438d..e9084321aa6 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java @@ -131,6 +131,8 @@ public abstract class AbstractGrpcTelemetryExporterTest { private static final AtomicInteger grpcEncodingServerAttempts = new AtomicInteger(); + private static volatile String grpcEncodingServerEncoding = "brotli"; + @RegisterExtension @Order(1) static final SelfSignedCertificateExtension certificate = new SelfSignedCertificateExtension(); @@ -243,7 +245,8 @@ private static void handleExport( } } - // A minimal server that returns grpc-encoding: brotli to test unsupported encoding handling. + // A minimal server that returns a configurable grpc-encoding to test encoding handling. + // The encoding is controlled via grpcEncodingServerEncoding (defaults to "brotli"). // Both OkHttpGrpcSender (our code) and UpstreamGrpcSender (grpc-java framework) reject unknown // encodings and fail the export with INTERNAL status. @RegisterExtension @@ -261,7 +264,7 @@ protected void configure(ServerBuilder sb) { return HttpResponse.of( ResponseHeaders.builder(HttpStatus.OK) .contentType(MediaType.parse("application/grpc+proto")) - .add("grpc-encoding", "brotli") + .add("grpc-encoding", grpcEncodingServerEncoding) .add("grpc-status", "0") .build(), HttpData.wrap(frame)); @@ -329,6 +332,7 @@ void reset() { attempts.set(0); httpRequests.clear(); grpcEncodingServerAttempts.set(0); + grpcEncodingServerEncoding = "brotli"; } @Test @@ -461,6 +465,36 @@ void unsupportedGrpcMessageEncoding() { } } + // Verifies that a response with flag=1 and grpc-encoding: identity is rejected with INTERNAL. + // Although identity is advertised in grpc-accept-encoding (meaning "no encoding applied"), + // a server setting the compressed flag to 1 is contradictory and should not be accepted. + @Test + @SuppressLogger(GrpcExporter.class) + void identityGrpcMessageEncodingWithCompressedFlagRejected() { + grpcEncodingServerEncoding = "identity"; + try (TelemetryExporter testExporter = + exporterBuilder() + .setEndpoint(grpcEncodingServer.httpUri().toString()) + .setRetryPolicy(null) + .build()) { + CompletableResultCode result = + testExporter + .export(Collections.singletonList(generateFakeTelemetry())) + .join(10, TimeUnit.SECONDS); + assertThat(result.isSuccess()).isFalse(); + assertThat(grpcEncodingServerAttempts).hasValue(1); + assertThat(result.getFailureThrowable()) + .isInstanceOfSatisfying( + FailedExportException.GrpcExportException.class, + ex -> + assertThat(requireNonNull(ex.getResponse())) + .satisfies( + grpcResponse -> + assertThat(grpcResponse.getStatusCode()) + .isEqualTo(GrpcStatusCode.INTERNAL))); + } + } + @Test void multipleItems() { List telemetry = new ArrayList<>(); diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java index 2a7981fcba7..22cbd520417 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java @@ -651,6 +651,25 @@ void responseBodyBoundsGzipCompressed() throws IOException { .hasMessageContaining("HTTP response body exceeded limit of")); } + @Test + void identityResponseContentEncoding() { + // Server responds with Content-Encoding: identity, which means no encoding. The export should + // succeed just like a response with no Content-Encoding header. + AbstractMessageLite responseBody = exporter.exportResponse(0); + httpErrors.add( + HttpResponse.of( + ResponseHeaders.builder(HttpStatus.OK) + .contentType(MediaType.PLAIN_TEXT_UTF_8) + .add("Content-Encoding", "identity") + .build(), + HttpData.wrap(responseBody.toByteArray()))); + CompletableResultCode result = + exporter + .export(Collections.singletonList(generateFakeTelemetry())) + .join(10, TimeUnit.SECONDS); + assertThat(result.isSuccess()).isTrue(); + } + @Test @SuppressLogger(HttpExporter.class) void unsupportedResponseContentEncoding() { diff --git a/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java b/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java index 2ffb296de9c..e5aefc9ef2f 100644 --- a/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java +++ b/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java @@ -350,7 +350,9 @@ private HttpResponse toHttpResponse(java.net.http.HttpResponse resp : (int) (maxResponseBodySize + 1); String contentEncoding = response.headers().firstValue("Content-Encoding").orElse(null); - if (contentEncoding != null && !"gzip".equalsIgnoreCase(contentEncoding)) { + if (contentEncoding != null + && !"gzip".equalsIgnoreCase(contentEncoding) + && !"identity".equalsIgnoreCase(contentEncoding)) { throw new UnsupportedContentEncodingException( "Unsupported Content-Encoding: " + contentEncoding); } diff --git a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java index 49c7fc10821..ad35eae4a60 100644 --- a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java +++ b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java @@ -160,7 +160,9 @@ private void handleResponse( Response response, Consumer onResponse, Consumer onError) { try (ResponseBody body = response.body()) { String contentEncoding = response.header("Content-Encoding"); - if (contentEncoding != null && !"gzip".equalsIgnoreCase(contentEncoding)) { + if (contentEncoding != null + && !"gzip".equalsIgnoreCase(contentEncoding) + && !"identity".equalsIgnoreCase(contentEncoding)) { onError.accept(new IOException("Unsupported Content-Encoding: " + contentEncoding)); return; }