From 3ee9a7c22dcb895d21b3b4226c811a05740a3726 Mon Sep 17 00:00:00 2001 From: Pierre Date: Wed, 28 Sep 2022 16:28:42 +0200 Subject: [PATCH] SONAR-17413 Compress telemetry payload --- .../org/sonar/process/ProcessProperties.java | 1 + .../server/telemetry/TelemetryClient.java | 41 +++++++++++- .../TelemetryClientCompressionTest.java | 62 +++++++++++++++++++ .../server/telemetry/TelemetryClientTest.java | 2 + 4 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientCompressionTest.java diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java index b51061ebc3b..fa5b7a898c4 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java @@ -168,6 +168,7 @@ public class ProcessProperties { SONAR_TELEMETRY_ENABLE("sonar.telemetry.enable", "true"), SONAR_TELEMETRY_URL("sonar.telemetry.url", "https://telemetry.sonarsource.com/sonarqube"), SONAR_TELEMETRY_FREQUENCY_IN_SECONDS("sonar.telemetry.frequencyInSeconds", "10800"), + SONAR_TELEMETRY_COMPRESSION("sonar.telemetry.compression", "true"), SONAR_UPDATECENTER_ACTIVATE("sonar.updatecenter.activate", "true"), diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryClient.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryClient.java index 69167d65841..c203a4cf5ab 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryClient.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryClient.java @@ -26,12 +26,16 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okio.BufferedSink; +import okio.GzipSink; +import okio.Okio; import org.sonar.api.Startable; import org.sonar.api.config.Configuration; import org.sonar.api.server.ServerSide; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; +import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_COMPRESSION; import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL; @ServerSide @@ -42,10 +46,11 @@ public class TelemetryClient implements Startable { private final OkHttpClient okHttpClient; private final Configuration config; private String serverUrl; + private boolean compression; public TelemetryClient(OkHttpClient okHttpClient, Configuration config) { - this.okHttpClient = okHttpClient; this.config = config; + this.okHttpClient = okHttpClient; } void upload(String json) throws IOException { @@ -68,12 +73,40 @@ public class TelemetryClient implements Startable { private Request buildHttpRequest(String json) { Request.Builder request = new Request.Builder(); + request.addHeader("Content-Encoding", "gzip"); + request.addHeader("Content-Type", "application/json"); request.url(serverUrl); RequestBody body = RequestBody.create(JSON, json); - request.post(body); + if (compression) { + request.post(gzip(body)); + } else { + request.post(body); + } return request.build(); } + private static RequestBody gzip(final RequestBody body) { + return new RequestBody() { + @Override + public MediaType contentType() { + return body.contentType(); + } + + @Override + public long contentLength() { + // We don't know the compressed length in advance! + return -1; + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); + body.writeTo(gzipSink); + gzipSink.close(); + } + }; + } + private static void execute(Call call) throws IOException { try (Response ignored = call.execute()) { // auto close connection to avoid leaked connection @@ -82,7 +115,9 @@ public class TelemetryClient implements Startable { @Override public void start() { - this.serverUrl = config.get(SONAR_TELEMETRY_URL.getKey()).orElseThrow(() -> new IllegalStateException(String.format("Setting '%s' must be provided.", SONAR_TELEMETRY_URL))); + this.serverUrl = config.get(SONAR_TELEMETRY_URL.getKey()) + .orElseThrow(() -> new IllegalStateException(String.format("Setting '%s' must be provided.", SONAR_TELEMETRY_URL))); + this.compression = config.getBoolean(SONAR_TELEMETRY_COMPRESSION.getKey()).orElse(true); } @Override diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientCompressionTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientCompressionTest.java new file mode 100644 index 00000000000..b463b00c91c --- /dev/null +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientCompressionTest.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.telemetry; + +import java.io.IOException; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import okio.GzipSource; +import okio.Okio; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.sonar.api.config.internal.MapSettings; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL; + +public class TelemetryClientCompressionTest { + + private final OkHttpClient okHttpClient = new OkHttpClient(); + private final MockWebServer telemetryServer = new MockWebServer(); + + @Test + public void payload_is_gzip_encoded() throws IOException, InterruptedException { + telemetryServer.enqueue(new MockResponse().setResponseCode(200)); + MapSettings settings = new MapSettings(); + settings.setProperty(SONAR_TELEMETRY_URL.getKey(), telemetryServer.url("/").toString()); + TelemetryClient underTest = new TelemetryClient(okHttpClient, settings.asConfig()); + underTest.start(); + underTest.upload("payload compressed with gzip"); + + RecordedRequest request = telemetryServer.takeRequest(); + + String contentType = Objects.requireNonNull(request.getHeader("content-type")); + assertThat(MediaType.parse(contentType)).isEqualTo(MediaType.parse("application/json; charset=utf-8")); + assertThat(request.getHeader("content-encoding")).isEqualTo("gzip"); + + GzipSource source = new GzipSource(request.getBody()); + String body = Okio.buffer(source).readUtf8(); + Assertions.assertThat(body).isEqualTo("payload compressed with gzip"); + } +} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientTest.java index d24e3c1b396..b39727bae36 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryClientTest.java @@ -32,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_COMPRESSION; import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL; public class TelemetryClientTest { @@ -48,6 +49,7 @@ public class TelemetryClientTest { public void upload() throws IOException { ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class); settings.setProperty(SONAR_TELEMETRY_URL.getKey(), TELEMETRY_URL); + settings.setProperty(SONAR_TELEMETRY_COMPRESSION.getKey(), false); underTest.start(); underTest.upload(JSON); -- 2.39.5