diff --git a/spring-cloud-sleuth-zipkin/pom.xml b/spring-cloud-sleuth-zipkin/pom.xml index c1fc3f8a5..9a2b3ba3d 100644 --- a/spring-cloud-sleuth-zipkin/pom.xml +++ b/spring-cloud-sleuth-zipkin/pom.xml @@ -191,6 +191,16 @@ aspectjweaver test + + org.testcontainers + junit-jupiter + test + + + io.projectreactor.netty + reactor-netty + test + diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/WebClientSender.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/WebClientSender.java index bad08821c..50ec00888 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/WebClientSender.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/WebClientSender.java @@ -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 diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/AbstractSenderTest.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/AbstractSenderTest.java new file mode 100644 index 000000000..0ddabe835 --- /dev/null +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/AbstractSenderTest.java @@ -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 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())); + } + +} diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSenderTest.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSenderTest.java index bc201c16a..e6e972c53 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSenderTest.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/RestTemplateSenderTest.java @@ -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 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 + "}"; } } diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/WebClientSenderTests.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/WebClientSenderTests.java new file mode 100644 index 000000000..98461e837 --- /dev/null +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/WebClientSenderTests.java @@ -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 + "}"; + } + +}