Adds missing content-type to webclient zipkin sender; fixes gh-2139

This commit is contained in:
Marcin Grzejszczak
2022-04-13 10:08:25 +02:00
parent 3323115ec0
commit 22be601b89
5 changed files with 224 additions and 91 deletions

View File

@@ -191,6 +191,16 @@
<artifactId>aspectjweaver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -37,8 +37,8 @@ public class WebClientSender extends HttpSender {
}
private static void post(String url, MediaType mediaType, byte[] json, WebClient webClient) {
webClient.post().uri(URI.create(url)).accept(mediaType).bodyValue(json).retrieve().toBodilessEntity()
.subscribe();
webClient.post().uri(URI.create(url)).accept(mediaType).contentType(mediaType).bodyValue(json).retrieve()
.toBodilessEntity().subscribe();
}
@Override

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.zipkin2;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import zipkin2.Call;
import zipkin2.Endpoint;
import zipkin2.Span;
import zipkin2.codec.Encoding;
import zipkin2.codec.SpanBytesEncoder;
import zipkin2.reporter.Sender;
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static zipkin2.codec.SpanBytesEncoder.JSON_V2;
abstract class AbstractSenderTest {
static final Span SPAN = Span.newBuilder().traceId("7180c278b62e8f6a216a2aea45d08fc9").parentId("6b221d5bc9e6496c")
.id("5b4185666d50f68b").name("get /backend").kind(Span.Kind.SERVER).shared(true)
.localEndpoint(Endpoint.newBuilder().serviceName("backend").ip("192.168.99.101").port(9000).build())
.timestamp(1472470996250000L).duration(100000L).putTag("http.method", "GET").putTag("http.path", "/backend")
.build();
public MockWebServer server = new MockWebServer();
String endpoint = this.server.url("/api/v2/spans").toString();
Sender sender = jsonSender();
abstract Sender jsonSender();
abstract Sender jsonSender(String mockedApiPath);
abstract Sender protoSender();
abstract String expectedToString();
abstract String expectedToStringWithNonEmptyApiPath(String mockedApiPath);
@AfterEach
void clean() throws IOException {
server.close();
}
/**
* Tests that json is not manipulated as a side-effect of using rest template.
* @throws Exception when span sending or receiving fails
*/
@Test
public void jsonIsNormal() throws Exception {
this.server.enqueue(new MockResponse());
send(SPAN).execute();
RecordedRequest request = this.server.takeRequest();
assertThat(request.getBody().readUtf8()).isEqualTo("[" + new String(JSON_V2.encode(SPAN), "UTF-8") + "]");
assertThat(request.getHeader("Content-Type")).isEqualTo("application/json");
}
@Test
public void proto3() throws Exception {
this.server.enqueue(new MockResponse());
this.sender = protoSender();
send(SPAN).execute();
RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(request.getHeader("Content-Type")).isEqualTo("application/x-protobuf");
// proto3 encoding of ListOfSpan is simply a repeated span entry
assertThat(request.getBody().readByteArray()).containsExactly(SpanBytesEncoder.PROTO3.encode(SPAN));
}
@Test
public void testWhereApiIsSetNonEmpty() {
final String mockedApiPath = "/test/v2";
final Sender senderWithMockedApiPath = jsonSender(mockedApiPath);
assertThat(senderWithMockedApiPath).hasToString(expectedToStringWithNonEmptyApiPath(mockedApiPath));
}
@Test
public void testWhereApiIsSetToEmpty() {
final String mockedApiPath = "";
final Sender senderWithMockedApiPath = jsonSender(mockedApiPath);
assertThat(senderWithMockedApiPath).hasToString(expectedToStringWithNonEmptyApiPath(mockedApiPath));
}
/**
* The output of toString() on {@link Sender} implementations appears in thread names
* created by {@link AsyncZipkinSpanHandler}. Since thread names are likely to be
* exposed in logs and other monitoring tools, care should be taken to ensure the
* toString() output is a reasonable length and does not contain sensitive
* information.
*/
@Test
public void toStringContainsOnlySenderTypeAndEndpoint() {
assertThat(this.sender).hasToString(expectedToString());
}
Call<Void> send(Span... spans) {
SpanBytesEncoder bytesEncoder = this.sender.encoding() == Encoding.JSON ? SpanBytesEncoder.JSON_V2
: SpanBytesEncoder.PROTO3;
return this.sender.sendSpans(Stream.of(spans).map(bytesEncoder::encode).collect(toList()));
}
}

View File

@@ -16,110 +16,41 @@
package org.springframework.cloud.sleuth.zipkin2;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import zipkin2.Call;
import zipkin2.Endpoint;
import zipkin2.Span;
import zipkin2.codec.Encoding;
import zipkin2.codec.SpanBytesEncoder;
import zipkin2.reporter.Sender;
import org.springframework.web.client.RestTemplate;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static zipkin2.codec.SpanBytesEncoder.JSON_V2;
import static zipkin2.codec.SpanBytesEncoder.PROTO3;
public class RestTemplateSenderTest {
class RestTemplateSenderTest extends AbstractSenderTest {
static final Span SPAN = Span.newBuilder().traceId("7180c278b62e8f6a216a2aea45d08fc9").parentId("6b221d5bc9e6496c")
.id("5b4185666d50f68b").name("get /backend").kind(Span.Kind.SERVER).shared(true)
.localEndpoint(Endpoint.newBuilder().serviceName("backend").ip("192.168.99.101").port(9000).build())
.timestamp(1472470996250000L).duration(100000L).putTag("http.method", "GET").putTag("http.path", "/backend")
.build();
public MockWebServer server = new MockWebServer();
String endpoint = this.server.url("/api/v2/spans").toString();
RestTemplateSender sender = new RestTemplateSender(new RestTemplate(), this.endpoint, null, JSON_V2);
@AfterEach
void clean() throws IOException {
server.close();
@Override
Sender jsonSender() {
return new RestTemplateSender(new RestTemplate(), this.endpoint, null, JSON_V2);
}
/**
* Tests that json is not manipulated as a side-effect of using rest template.
* @throws Exception when span sending or receiving fails
*/
@Test
public void jsonIsNormal() throws Exception {
this.server.enqueue(new MockResponse());
send(SPAN).execute();
assertThat(this.server.takeRequest().getBody().readUtf8())
.isEqualTo("[" + new String(JSON_V2.encode(SPAN), "UTF-8") + "]");
@Override
Sender jsonSender(String mockedApiPath) {
return new RestTemplateSender(new RestTemplate(), this.endpoint, mockedApiPath, JSON_V2);
}
@Test
public void proto3() throws Exception {
this.server.enqueue(new MockResponse());
this.sender = new RestTemplateSender(new RestTemplate(), this.endpoint, "", PROTO3);
send(SPAN).execute();
RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(request.getHeader("Content-Type")).isEqualTo("application/x-protobuf");
// proto3 encoding of ListOfSpan is simply a repeated span entry
assertThat(request.getBody().readByteArray()).containsExactly(SpanBytesEncoder.PROTO3.encode(SPAN));
@Override
Sender protoSender() {
return new RestTemplateSender(new RestTemplate(), this.endpoint, "", PROTO3);
}
@Test
public void testWhereApiIsSetNonEmpty() {
final String mockedApiPath = "/test/v2";
final RestTemplateSender senderWithMockedApiPath = new RestTemplateSender(new RestTemplate(), this.endpoint,
mockedApiPath, JSON_V2);
assertThat(senderWithMockedApiPath.toString())
.isEqualTo("RestTemplateSender{" + this.endpoint + mockedApiPath + "}");
@Override
String expectedToString() {
return "RestTemplateSender{" + this.endpoint + "/api/v2/spans}";
}
@Test
public void testWhereApiIsSetToEmpty() {
final String mockedApiPath = "";
final RestTemplateSender senderWithMockedApiPath = new RestTemplateSender(new RestTemplate(), this.endpoint,
mockedApiPath, JSON_V2);
assertThat(senderWithMockedApiPath.toString()).isEqualTo("RestTemplateSender{" + this.endpoint + "}");
}
/**
* The output of toString() on {@link Sender} implementations appears in thread names
* created by {@link AsyncZipkinSpanHandler}. Since thread names are likely to be
* exposed in logs and other monitoring tools, care should be taken to ensure the
* toString() output is a reasonable length and does not contain sensitive
* information.
*/
@Test
public void toStringContainsOnlySenderTypeAndEndpoint() {
assertThat(sender.toString()).isEqualTo("RestTemplateSender{" + this.endpoint + "/api/v2/spans}");
}
Call<Void> send(Span... spans) {
SpanBytesEncoder bytesEncoder = this.sender.encoding() == Encoding.JSON ? SpanBytesEncoder.JSON_V2
: SpanBytesEncoder.PROTO3;
return this.sender.sendSpans(Stream.of(spans).map(bytesEncoder::encode).collect(toList()));
@Override
String expectedToStringWithNonEmptyApiPath(String mockedApiPath) {
if ("".equals(mockedApiPath)) {
return "RestTemplateSender{" + this.endpoint + "}";
}
return "RestTemplateSender{" + this.endpoint + mockedApiPath + "}";
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.zipkin2;
import zipkin2.reporter.Sender;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import static zipkin2.codec.SpanBytesEncoder.JSON_V2;
import static zipkin2.codec.SpanBytesEncoder.PROTO3;
class WebClientSenderTests extends AbstractSenderTest {
@Override
Sender jsonSender() {
return new WebClientSender(WebClient.builder().clientConnector(new ReactorClientHttpConnector()).build(),
this.endpoint, null, JSON_V2);
}
@Override
Sender jsonSender(String mockedApiPath) {
return new WebClientSender(WebClient.builder().clientConnector(new ReactorClientHttpConnector()).build(),
this.endpoint, mockedApiPath, JSON_V2);
}
@Override
Sender protoSender() {
return new WebClientSender(WebClient.builder().clientConnector(new ReactorClientHttpConnector()).build(),
this.endpoint, "", PROTO3);
}
@Override
String expectedToString() {
return "WebClientSender{" + this.endpoint + "/api/v2/spans}";
}
@Override
String expectedToStringWithNonEmptyApiPath(String mockedApiPath) {
if ("".equals(mockedApiPath)) {
return "WebClientSender{" + this.endpoint + "}";
}
return "WebClientSender{" + this.endpoint + mockedApiPath + "}";
}
}