Skip to content

Commit b234627

Browse files
authored
Merge pull request #6 from sekikatsu36/feature/add-traceparent-header-to-apache-httpclinet
add traceparent header to apache-httpclinet
2 parents ee9efe3 + 07729a7 commit b234627

File tree

6 files changed

+190
-20
lines changed

6 files changed

+190
-20
lines changed

jvm/apache-httpclient/README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,20 @@
44

55
Add `M3TracingHttpInterceptor` as request/response interceptor of HttpClient.
66

7-
```java
7+
### httpclient 4.2-
8+
9+
```java:
810
// CAUTION: Must setup as BOTH interceptor otherwise it may cause memory leak.
911
httpclient.addRequestInterceptor(M3TracingHttpInterceptor.INSTANCE);
1012
httpclient.addResponseInterceptor(M3TracingHttpInterceptor.INSTANCE);
1113
```
14+
15+
### httpclient 4.3+
16+
17+
```java
18+
CloseableHttpClient httpClient = HttpClientBuilder.create()
19+
.addInterceptorFirst((HttpRequestInterceptor) M3TracingHttpInterceptor.INSTANCE)
20+
.addInterceptorLast((HttpResponseInterceptor) M3TracingHttpInterceptor.INSTANCE)
21+
.build();
22+
```
23+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.m3.tracing.apache.httpclient
2+
3+
import com.m3.tracing.http.HttpRequestInfo
4+
import com.m3.tracing.http.HttpRequestMetadataKey
5+
import org.apache.http.HttpRequest
6+
import org.apache.http.client.methods.HttpUriRequest
7+
import org.apache.http.client.protocol.HttpClientContext
8+
import org.apache.http.protocol.HttpContext
9+
10+
open class ApacheHttpRequestInfo(protected val req: HttpRequest, protected val context: HttpContext) : HttpRequestInfo {
11+
override fun tryGetHeader(name: String): String? = req.getFirstHeader(name)?.value
12+
override fun trySetHeader(name: String, value: String) = req.setHeader(name, value)
13+
14+
@Suppress("UNCHECKED_CAST", "IMPLICIT_ANY")
15+
override fun <T> tryGetMetadata(key: HttpRequestMetadataKey<T>): T? = when (key) {
16+
HttpRequestMetadataKey.Method -> req.requestLine?.method as T?
17+
HttpRequestMetadataKey.Path -> getPath() as T?
18+
HttpRequestMetadataKey.Host -> if (context is HttpClientContext) context.targetHost?.hostName as T? else null
19+
HttpRequestMetadataKey.Url -> if (context is HttpClientContext) "${context.targetHost}${getPath() ?: ""}" as T? else null
20+
21+
else -> null
22+
}
23+
24+
// HttpRequest.requestLine.url contains querystring, so need to be removed
25+
private fun getPath(): String? = if (req is HttpUriRequest) req.uri?.path else req.requestLine?.uri?.split("?")?.elementAt(0)
26+
}

jvm/apache-httpclient/src/main/kotlin/com/m3/tracing/apache/httpclient/M3TracingHttpInterceptor.kt

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package com.m3.tracing.apache.httpclient
33
import com.m3.tracing.M3Tracer
44
import com.m3.tracing.M3TracerFactory
55
import com.m3.tracing.TraceSpan
6+
import com.m3.tracing.http.HttpRequestMetadataKey
67
import org.apache.http.HttpRequest
78
import org.apache.http.HttpRequestInterceptor
89
import org.apache.http.HttpResponse
910
import org.apache.http.HttpResponseInterceptor
10-
import org.apache.http.client.methods.HttpUriRequest
1111
import org.apache.http.protocol.HttpContext
1212
import org.slf4j.LoggerFactory
1313

@@ -19,22 +19,24 @@ open class M3TracingHttpInterceptor(
1919
protected val tracer: M3Tracer
2020
) : HttpRequestInterceptor, HttpResponseInterceptor {
2121
companion object {
22-
@JvmStatic
22+
@JvmField
2323
public val INSTANCE = M3TracingHttpInterceptor()
2424

2525
private val currentSpan = ThreadLocal<TraceSpan>()
2626
private val logger = LoggerFactory.getLogger(M3TracingHttpInterceptor::class.java)
2727
}
2828

29-
constructor(): this(M3TracerFactory.get())
29+
constructor() : this(M3TracerFactory.get())
3030

3131
override fun process(request: HttpRequest, context: HttpContext) {
32-
val span = tracer.startSpan(createSpanName(request))
32+
val requestInfo = ApacheHttpRequestInfo(request, context)
33+
val span = tracer.processOutgoingHttpRequest(requestInfo)
3334
currentSpan.set(span) // Set to ThreadLocal ASAP to prevent leak
3435

3536
doQuietly {
36-
span["method"] = request.requestLine.method
37-
span["uri"] = request.requestLine.uri
37+
span["client"] = "m3-tracing:apache-httpclient"
38+
span["method"] = requestInfo.tryGetMetadata(HttpRequestMetadataKey.Method)
39+
span["path"] = requestInfo.tryGetMetadata(HttpRequestMetadataKey.Path)
3840
}
3941
}
4042

@@ -49,17 +51,6 @@ open class M3TracingHttpInterceptor(
4951
span.close()
5052
}
5153

52-
53-
private fun createSpanName(request: HttpRequest): String {
54-
// Intentionally excluded queryString because it might contain dynamic string
55-
// Dynamic span name makes runningSpan table so huge
56-
return if (request is HttpUriRequest) {
57-
"HTTP ${request.method} ${request.uri.host}"
58-
} else {
59-
"HTTP ${request.requestLine.method}"
60-
}
61-
}
62-
6354
/**
6455
* `On Error Resume Next` in 21st century.
6556
*/
@@ -70,4 +61,4 @@ open class M3TracingHttpInterceptor(
7061
logger.error("Failed to update Span.", e)
7162
}
7263
}
73-
}
64+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package com.m3.tracing.apache.httpclient
2+
3+
import com.google.common.truth.Truth
4+
import com.m3.tracing.http.HttpRequestMetadataKey
5+
import org.apache.http.HttpHost
6+
import org.apache.http.HttpRequest
7+
import org.apache.http.RequestLine
8+
import org.apache.http.client.methods.HttpUriRequest
9+
import org.apache.http.client.protocol.HttpClientContext
10+
import org.apache.http.protocol.HttpContext
11+
import org.junit.jupiter.api.Test
12+
import org.junit.jupiter.api.extension.ExtendWith
13+
import org.mockito.Mock
14+
import org.mockito.Mockito
15+
import org.mockito.Mockito.mock
16+
import org.mockito.junit.jupiter.MockitoExtension
17+
import java.net.URI
18+
19+
@ExtendWith(MockitoExtension::class)
20+
class ApacheHttpRequestInfoTest {
21+
22+
@Mock
23+
lateinit var uriRequest: HttpUriRequest
24+
25+
@Mock
26+
lateinit var nonUriRequest: HttpRequest
27+
28+
@Mock
29+
lateinit var clientContext: HttpClientContext
30+
31+
@Mock
32+
lateinit var nonClientContext: HttpContext
33+
34+
@Test
35+
fun `each attribute is set properly for HttpUriRequest`() {
36+
37+
val requestLine = mock(RequestLine::class.java)
38+
val uri = mock(URI::class.java)
39+
val host = mock(HttpHost::class.java)
40+
41+
Mockito.`when`(uriRequest.requestLine).thenReturn(requestLine)
42+
Mockito.`when`(uriRequest.uri).thenReturn(uri)
43+
Mockito.`when`(clientContext.targetHost).thenReturn(host)
44+
Mockito.`when`(uri.path).thenReturn("/foo/bar.html")
45+
Mockito.`when`(requestLine.method).thenReturn("GET")
46+
Mockito.`when`(host.hostName).thenReturn("example.com")
47+
Mockito.`when`(host.toString()).thenReturn("http://example.com")
48+
49+
val req = ApacheHttpRequestInfo(uriRequest, clientContext)
50+
51+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Host)).isEqualTo("example.com")
52+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Method)).isEqualTo("GET")
53+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Path)).isEqualTo("/foo/bar.html")
54+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Url)).isEqualTo("http://example.com/foo/bar.html")
55+
}
56+
57+
@Test
58+
fun `each attribute is set properly for Non-HttpUriRequest`() {
59+
60+
val requestLine = mock(RequestLine::class.java)
61+
62+
Mockito.`when`(nonUriRequest.requestLine).thenReturn(requestLine)
63+
Mockito.`when`(requestLine.method).thenReturn("GET")
64+
Mockito.`when`(requestLine.uri).thenReturn("/foo/bar.html?param=value")
65+
66+
var req = ApacheHttpRequestInfo(nonUriRequest, nonClientContext)
67+
68+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Host)).isNull()
69+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Method)).isEqualTo("GET")
70+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Path)).isEqualTo("/foo/bar.html")
71+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Url)).isNull()
72+
73+
Mockito.`when`(requestLine.uri).thenReturn("/")
74+
75+
req = ApacheHttpRequestInfo(nonUriRequest, nonClientContext)
76+
77+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Host)).isNull()
78+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Method)).isEqualTo("GET")
79+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Path)).isEqualTo("/")
80+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Url)).isNull()
81+
}
82+
83+
@Test
84+
fun `no exception when attributes in HttpUriRequest are null`() {
85+
86+
val requestLine = mock(RequestLine::class.java)
87+
val uri = mock(URI::class.java)
88+
val host = mock(HttpHost::class.java)
89+
90+
Mockito.`when`(uriRequest.requestLine).thenReturn(requestLine)
91+
Mockito.`when`(uriRequest.uri).thenReturn(uri)
92+
Mockito.`when`(clientContext.targetHost).thenReturn(host)
93+
Mockito.`when`(uri.path).thenReturn(null)
94+
Mockito.`when`(requestLine.method).thenReturn(null)
95+
Mockito.`when`(host.hostName).thenReturn(null)
96+
Mockito.`when`(host.toString()).thenReturn(null)
97+
98+
var req = ApacheHttpRequestInfo(uriRequest, clientContext)
99+
100+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Host)).isNull()
101+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Method)).isNull()
102+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Path)).isNull()
103+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Url)).isEqualTo("null")
104+
105+
Mockito.`when`(uriRequest.requestLine).thenReturn(null)
106+
Mockito.`when`(uriRequest.uri).thenReturn(null)
107+
Mockito.`when`(clientContext.targetHost).thenReturn(null)
108+
109+
req = ApacheHttpRequestInfo(uriRequest, clientContext)
110+
111+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Host)).isNull()
112+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Method)).isNull()
113+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Path)).isNull()
114+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Url)).isEqualTo("null")
115+
}
116+
117+
@Test
118+
fun `no exception when attributes in non-HttpUriRequest are null`() {
119+
120+
val requestLine = mock(RequestLine::class.java)
121+
122+
Mockito.`when`(nonUriRequest.requestLine).thenReturn(requestLine)
123+
Mockito.`when`(requestLine.method).thenReturn(null)
124+
Mockito.`when`(requestLine.uri).thenReturn(null)
125+
126+
val req = ApacheHttpRequestInfo(nonUriRequest, nonClientContext)
127+
128+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Host)).isNull()
129+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Method)).isNull()
130+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Path)).isNull()
131+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Url)).isNull()
132+
133+
Mockito.`when`(nonUriRequest.requestLine).thenReturn(null)
134+
135+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Host)).isNull()
136+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Method)).isNull()
137+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Path)).isNull()
138+
Truth.assertThat(req.tryGetMetadata(HttpRequestMetadataKey.Url)).isNull()
139+
}
140+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
handlers = org.slf4j.bridge.SLF4JBridgeHandler
1+
handlers=org.slf4j.bridge.SLF4JBridgeHandler
22
.level=INFO
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mock-maker-inline

0 commit comments

Comments
 (0)