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"),
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
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 {
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
@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
--- /dev/null
+/*
+ * 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");
+ }
+}
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 {
public void upload() throws IOException {
ArgumentCaptor<Request> 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);