Skip to content

Commit 013e148

Browse files
committed
Implement JDK HttpClient based Zipkin sender
Closes gh-39545
1 parent 52648d9 commit 013e148

File tree

12 files changed

+392
-10
lines changed

12 files changed

+392
-10
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/HttpSender.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -74,7 +74,7 @@ protected void postSpans(URI endpoint, byte[] body) throws IOException {
7474
* @param headers headers for the POST request
7575
* @param body list of possibly gzipped, encoded spans.
7676
*/
77-
abstract void postSpans(URI endpoint, HttpHeaders headers, byte[] body);
77+
abstract void postSpans(URI endpoint, HttpHeaders headers, byte[] body) throws IOException;
7878

7979
HttpHeaders getDefaultHeaders() {
8080
HttpHeaders headers = new HttpHeaders();

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurations.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
1818

19+
import java.net.http.HttpClient;
20+
import java.net.http.HttpClient.Builder;
21+
1922
import brave.Tag;
2023
import brave.Tags;
2124
import brave.handler.MutableSpan;
@@ -53,7 +56,7 @@ class ZipkinConfigurations {
5356

5457
@Configuration(proxyBeanMethods = false)
5558
@Import({ UrlConnectionSenderConfiguration.class, WebClientSenderConfiguration.class,
56-
RestTemplateSenderConfiguration.class })
59+
RestTemplateSenderConfiguration.class, HttpClientSenderConfiguration.class })
5760
static class SenderConfiguration {
5861

5962
}
@@ -90,6 +93,7 @@ static class RestTemplateSenderConfiguration {
9093

9194
@Bean
9295
@ConditionalOnMissingBean(BytesMessageSender.class)
96+
@SuppressWarnings("removal")
9397
ZipkinRestTemplateSender restTemplateSender(ZipkinProperties properties, Encoding encoding,
9498
ObjectProvider<ZipkinRestTemplateBuilderCustomizer> customizers,
9599
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider,
@@ -106,6 +110,7 @@ ZipkinRestTemplateSender restTemplateSender(ZipkinProperties properties, Encodin
106110
restTemplateBuilder.build());
107111
}
108112

113+
@SuppressWarnings("removal")
109114
private RestTemplateBuilder applyCustomizers(RestTemplateBuilder restTemplateBuilder,
110115
ObjectProvider<ZipkinRestTemplateBuilderCustomizer> customizers) {
111116
Iterable<ZipkinRestTemplateBuilderCustomizer> orderedCustomizers = () -> customizers.orderedStream()
@@ -126,6 +131,7 @@ static class WebClientSenderConfiguration {
126131

127132
@Bean
128133
@ConditionalOnMissingBean(BytesMessageSender.class)
134+
@SuppressWarnings("removal")
129135
ZipkinWebClientSender webClientSender(ZipkinProperties properties, Encoding encoding,
130136
ObjectProvider<ZipkinWebClientBuilderCustomizer> customizers,
131137
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider,
@@ -142,6 +148,29 @@ ZipkinWebClientSender webClientSender(ZipkinProperties properties, Encoding enco
142148

143149
}
144150

151+
@Configuration(proxyBeanMethods = false)
152+
@ConditionalOnClass(HttpClient.class)
153+
@EnableConfigurationProperties(ZipkinProperties.class)
154+
static class HttpClientSenderConfiguration {
155+
156+
@Bean
157+
@ConditionalOnMissingBean(BytesMessageSender.class)
158+
ZipkinHttpClientSender httpClientSender(ZipkinProperties properties, Encoding encoding,
159+
ObjectProvider<ZipkinHttpClientBuilderCustomizer> customizers,
160+
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider,
161+
ObjectProvider<HttpEndpointSupplier.Factory> endpointSupplierFactoryProvider) {
162+
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
163+
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
164+
HttpEndpointSupplier.Factory endpointSupplierFactory = endpointSupplierFactoryProvider
165+
.getIfAvailable(HttpEndpointSuppliers::constantFactory);
166+
Builder httpClientBuilder = HttpClient.newBuilder().connectTimeout(properties.getConnectTimeout());
167+
customizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder));
168+
return new ZipkinHttpClientSender(encoding, endpointSupplierFactory, connectionDetails.getSpanEndpoint(),
169+
httpClientBuilder.build(), properties.getReadTimeout());
170+
}
171+
172+
}
173+
145174
@Configuration(proxyBeanMethods = false)
146175
@ConditionalOnClass(AsyncZipkinSpanHandler.class)
147176
static class BraveConfiguration {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2012-2024 the original author or 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+
* https://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 org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
18+
19+
import java.net.http.HttpClient;
20+
21+
/**
22+
* Callback interface that can be implemented by beans wishing to customize the
23+
* {@link HttpClient.Builder} used to send spans to Zipkin.
24+
*
25+
* @author Moritz Halbritter
26+
* @since 3.3.0
27+
*/
28+
@FunctionalInterface
29+
public interface ZipkinHttpClientBuilderCustomizer {
30+
31+
/**
32+
* Customize the http client builder.
33+
* @param httpClient the http client builder to customize
34+
*/
35+
void customize(HttpClient.Builder httpClient);
36+
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2012-2024 the original author or 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+
* https://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 org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
18+
19+
import java.io.IOException;
20+
import java.net.URI;
21+
import java.net.http.HttpClient;
22+
import java.net.http.HttpRequest;
23+
import java.net.http.HttpRequest.BodyPublishers;
24+
import java.net.http.HttpRequest.Builder;
25+
import java.net.http.HttpResponse;
26+
import java.net.http.HttpResponse.BodyHandlers;
27+
import java.time.Duration;
28+
29+
import zipkin2.reporter.Encoding;
30+
import zipkin2.reporter.HttpEndpointSupplier.Factory;
31+
32+
import org.springframework.http.HttpHeaders;
33+
34+
/**
35+
* A {@link HttpSender} which uses the JDK {@link HttpClient} for HTTP communication.
36+
*
37+
* @author Moritz Halbritter
38+
*/
39+
class ZipkinHttpClientSender extends HttpSender {
40+
41+
private final HttpClient httpClient;
42+
43+
private final Duration readTimeout;
44+
45+
ZipkinHttpClientSender(Encoding encoding, Factory endpointSupplierFactory, String endpoint, HttpClient httpClient,
46+
Duration readTimeout) {
47+
super(encoding, endpointSupplierFactory, endpoint);
48+
this.httpClient = httpClient;
49+
this.readTimeout = readTimeout;
50+
}
51+
52+
@Override
53+
void postSpans(URI endpoint, HttpHeaders headers, byte[] body) throws IOException {
54+
Builder request = HttpRequest.newBuilder()
55+
.POST(BodyPublishers.ofByteArray(body))
56+
.uri(endpoint)
57+
.timeout(this.readTimeout);
58+
headers.forEach((key, values) -> values.forEach((value) -> request.header(key, value)));
59+
try {
60+
HttpResponse<Void> response = this.httpClient.send(request.build(), BodyHandlers.discarding());
61+
if (response.statusCode() / 100 != 2) {
62+
throw new IOException("Expected HTTP status 2xx, got %d".formatted(response.statusCode()));
63+
}
64+
}
65+
catch (InterruptedException ex) {
66+
Thread.currentThread().interrupt();
67+
throw new IOException("Got interrupted while sending spans", ex);
68+
}
69+
}
70+
71+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateBuilderCustomizer.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,8 +24,11 @@
2424
*
2525
* @author Marcin Grzejszczak
2626
* @since 3.0.0
27+
* @deprecated since 3.3.0 for removal in 3.5.0 in favor of
28+
* {@link ZipkinHttpClientBuilderCustomizer}
2729
*/
2830
@FunctionalInterface
31+
@Deprecated(since = "3.3.0", forRemoval = true)
2932
public interface ZipkinRestTemplateBuilderCustomizer {
3033

3134
/**

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinRestTemplateSender.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
3232
* @author Moritz Halbritter
3333
* @author Stefan Bratanov
3434
*/
35+
@Deprecated(since = "3.3.0", forRemoval = true)
3536
class ZipkinRestTemplateSender extends HttpSender {
3637

3738
private final RestTemplate restTemplate;

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientBuilderCustomizer.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,8 +25,11 @@
2525
*
2626
* @author Marcin Grzejszczak
2727
* @since 3.0.0
28+
* @deprecated since 3.3.0 for removal in 3.5.0 in favor of
29+
* {@link ZipkinHttpClientBuilderCustomizer}
2830
*/
2931
@FunctionalInterface
32+
@Deprecated(since = "3.3.0", forRemoval = true)
3033
public interface ZipkinWebClientBuilderCustomizer {
3134

3235
/**

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinWebClientSender.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
* @author Stefan Bratanov
3232
* @author Moritz Halbritter
3333
*/
34+
@Deprecated(since = "3.3.0", forRemoval = true)
3435
class ZipkinWebClientSender extends HttpSender {
3536

3637
private final WebClient webClient;

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinConfigurationsSenderConfigurationTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
*
5151
* @author Moritz Halbritter
5252
*/
53+
@SuppressWarnings("removal")
5354
class ZipkinConfigurationsSenderConfigurationTests {
5455

5556
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
@@ -70,6 +71,20 @@ void shouldSupplyBeans() {
7071
});
7172
}
7273

74+
@Test
75+
void shouldUseHttpClientIfUrlSenderIsNotAvailable() {
76+
this.contextRunner.withUserConfiguration(HttpClientConfiguration.class)
77+
.withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection", "org.springframework.web.client",
78+
"org.springframework.web.reactive.function.client"))
79+
.run((context) -> {
80+
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
81+
assertThat(context).hasSingleBean(BytesMessageSender.class);
82+
assertThat(context).hasSingleBean(ZipkinHttpClientSender.class);
83+
then(context.getBean(ZipkinHttpClientBuilderCustomizer.class)).should()
84+
.customize(ArgumentMatchers.any());
85+
});
86+
}
87+
7388
@Test
7489
void shouldPreferWebClientSenderIfWebApplicationIsReactiveAndUrlSenderIsNotAvailable() {
7590
this.reactiveContextRunner.withUserConfiguration(RestTemplateConfiguration.class, WebClientConfiguration.class)
@@ -220,6 +235,16 @@ ZipkinWebClientBuilderCustomizer webClientBuilder() {
220235

221236
}
222237

238+
@Configuration(proxyBeanMethods = false)
239+
private static final class HttpClientConfiguration {
240+
241+
@Bean
242+
ZipkinHttpClientBuilderCustomizer httpClientBuilderCustomizer() {
243+
return mock(ZipkinHttpClientBuilderCustomizer.class);
244+
}
245+
246+
}
247+
223248
@Configuration(proxyBeanMethods = false)
224249
private static final class CustomConfiguration {
225250

0 commit comments

Comments
 (0)