Skip to content

Commit e16b79e

Browse files
authoredSep 10, 2024
OkHTTP3 - fix response charset and adding support for gzip compression (#405)
* fix response charset and adding support for gzip compression * adding tests * adding support for other clients * fixing apacheasync http client test * disabling flaky tests
1 parent 5813589 commit e16b79e

File tree

49 files changed

+1515
-172
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1515
-172
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright The Hypertrace Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opentelemetry.instrumentation.hypertrace.apachehttpasyncclient;
18+
19+
import io.opentelemetry.proto.trace.v1.Span;
20+
import java.io.BufferedReader;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.io.InputStreamReader;
24+
import java.nio.charset.StandardCharsets;
25+
import java.util.List;
26+
import java.util.concurrent.ExecutionException;
27+
import java.util.concurrent.Future;
28+
import java.util.concurrent.TimeoutException;
29+
import java.util.zip.GZIPInputStream;
30+
import org.apache.http.HttpResponse;
31+
import org.apache.http.client.methods.HttpGet;
32+
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
33+
import org.apache.http.impl.nio.client.HttpAsyncClients;
34+
import org.hypertrace.agent.testing.AbstractInstrumenterTest;
35+
import org.hypertrace.agent.testing.TestHttpServer;
36+
import org.junit.jupiter.api.AfterAll;
37+
import org.junit.jupiter.api.Assertions;
38+
import org.junit.jupiter.api.BeforeAll;
39+
import org.junit.jupiter.api.Test;
40+
41+
class ApacheAsyncClientGzipHandlingTest extends AbstractInstrumenterTest {
42+
43+
private static final TestHttpServer testHttpServer = new TestHttpServer();
44+
45+
private static final CloseableHttpAsyncClient client = HttpAsyncClients.createDefault();
46+
47+
@BeforeAll
48+
public static void startServer() throws Exception {
49+
testHttpServer.start();
50+
client.start();
51+
}
52+
53+
@AfterAll
54+
public static void closeServer() throws Exception {
55+
testHttpServer.close();
56+
}
57+
58+
@Test
59+
public void getGzipResponse()
60+
throws ExecutionException, InterruptedException, TimeoutException, IOException {
61+
HttpGet getRequest =
62+
new HttpGet(String.format("http://localhost:%s/gzip", testHttpServer.port()));
63+
getRequest.addHeader("foo", "bar");
64+
Future<HttpResponse> futureResponse =
65+
client.execute(
66+
getRequest, new ApacheAsyncClientInstrumentationModuleTest.NoopFutureCallback());
67+
68+
HttpResponse response = futureResponse.get();
69+
Assertions.assertEquals(200, response.getStatusLine().getStatusCode());
70+
try (InputStream gzipStream = new GZIPInputStream(response.getEntity().getContent())) {
71+
String responseBody = readInputStream(gzipStream);
72+
Assertions.assertEquals(TestHttpServer.GzipHandler.RESPONSE_BODY, responseBody);
73+
}
74+
75+
TEST_WRITER.waitForTraces(1);
76+
// exclude server spans
77+
List<List<Span>> traces =
78+
TEST_WRITER.waitForSpans(
79+
2,
80+
span ->
81+
span.getKind().equals(Span.SpanKind.SPAN_KIND_SERVER)
82+
|| span.getAttributesList().stream()
83+
.noneMatch(
84+
keyValue ->
85+
keyValue.getKey().equals("http.response.header.content-encoding")
86+
&& keyValue.getValue().getStringValue().contains("gzip")));
87+
Assertions.assertEquals(1, traces.size());
88+
Assertions.assertEquals(2, traces.get(0).size());
89+
Span clientSpan = traces.get(0).get(1);
90+
Span responseBodySpan = traces.get(0).get(0);
91+
if (traces.get(0).get(0).getKind().equals(Span.SpanKind.SPAN_KIND_CLIENT)) {
92+
clientSpan = traces.get(0).get(0);
93+
responseBodySpan = traces.get(0).get(1);
94+
}
95+
96+
Assertions.assertEquals(
97+
"test-value",
98+
TEST_WRITER
99+
.getAttributesMap(clientSpan)
100+
.get("http.response.header.test-response-header")
101+
.getStringValue());
102+
Assertions.assertEquals(
103+
"bar",
104+
TEST_WRITER.getAttributesMap(clientSpan).get("http.request.header.foo").getStringValue());
105+
Assertions.assertNull(TEST_WRITER.getAttributesMap(clientSpan).get("http.request.body"));
106+
107+
Assertions.assertEquals(
108+
TestHttpServer.GzipHandler.RESPONSE_BODY,
109+
TEST_WRITER.getAttributesMap(responseBodySpan).get("http.response.body").getStringValue());
110+
}
111+
112+
private String readInputStream(InputStream inputStream) throws IOException {
113+
StringBuilder textBuilder = new StringBuilder();
114+
115+
try (BufferedReader reader =
116+
new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
117+
int c;
118+
while ((c = reader.read()) != -1) {
119+
textBuilder.append((char) c);
120+
}
121+
}
122+
return textBuilder.toString();
123+
}
124+
}

Diff for: ‎instrumentation/apache-httpclient-4.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/apachehttpclient/v4_0/ApacheHttpClientUtils.java

+37-14
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
import io.opentelemetry.api.trace.Span;
2121
import io.opentelemetry.javaagent.instrumentation.hypertrace.apachehttpclient.v4_0.ApacheHttpClientObjectRegistry.SpanAndAttributeKey;
2222
import java.io.IOException;
23-
import java.io.UnsupportedEncodingException;
23+
import java.io.InputStream;
24+
import java.io.InputStreamReader;
25+
import java.io.OutputStreamWriter;
2426
import java.nio.charset.Charset;
2527
import java.util.function.Function;
28+
import java.util.zip.GZIPInputStream;
2629
import org.apache.http.Header;
2730
import org.apache.http.HeaderIterator;
2831
import org.apache.http.HttpEntity;
@@ -100,26 +103,32 @@ public static void traceEntity(
100103
if (contentType == null || !ContentTypeUtils.shouldCapture(contentType.getValue())) {
101104
return;
102105
}
103-
104106
String charsetStr = ContentTypeUtils.parseCharset(contentType.getValue());
105107
Charset charset = ContentTypeCharsetUtils.toCharset(charsetStr);
106-
108+
// Get the content encoding header and check if it's gzip
109+
Header contentEncoding = entity.getContentEncoding();
110+
boolean isGzipEncoded =
111+
contentEncoding != null
112+
&& contentEncoding.getValue() != null
113+
&& contentEncoding.getValue().toLowerCase().contains("gzip");
107114
if (entity.isRepeatable()) {
108115
try {
109-
BoundedByteArrayOutputStream byteArrayOutputStream =
110-
BoundedBuffersFactory.createStream(charset);
111-
entity.writeTo(byteArrayOutputStream);
112-
113-
try {
114-
String body = byteArrayOutputStream.toStringWithSuppliedCharset();
115-
span.setAttribute(bodyAttributeKey, body);
116-
} catch (UnsupportedEncodingException e) {
117-
log.error("Could not parse charset from encoding {}", charsetStr, e);
116+
InputStream contentStream = entity.getContent();
117+
if (isGzipEncoded) {
118+
try {
119+
contentStream = new GZIPInputStream(contentStream);
120+
} catch (IOException e) {
121+
log.error("Failed to create GZIPInputStream", e);
122+
return;
123+
}
118124
}
125+
126+
String body = readInputStream(contentStream, charset);
127+
span.setAttribute(bodyAttributeKey, body);
128+
119129
} catch (IOException e) {
120-
log.error("Could not read request input stream from repeatable request entity/body", e);
130+
throw new RuntimeException(e);
121131
}
122-
123132
return;
124133
}
125134

@@ -133,4 +142,18 @@ public static void traceEntity(
133142
ApacheHttpClientObjectRegistry.entityToSpan.put(
134143
entity, new SpanAndAttributeKey(span, bodyAttributeKey));
135144
}
145+
146+
public static String readInputStream(InputStream inputStream, Charset charset)
147+
throws IOException {
148+
BoundedByteArrayOutputStream outputStream = BoundedBuffersFactory.createStream(charset);
149+
try (InputStreamReader reader = new InputStreamReader(inputStream, charset);
150+
OutputStreamWriter writer = new OutputStreamWriter(outputStream, charset)) {
151+
int c;
152+
while ((c = reader.read()) != -1) {
153+
writer.write(c);
154+
}
155+
writer.flush();
156+
}
157+
return outputStream.toStringWithSuppliedCharset();
158+
}
136159
}

0 commit comments

Comments
 (0)