From 5636eaac4cadaf85f73d454539c5fe0141e4296d Mon Sep 17 00:00:00 2001 From: buongarzoni Date: Sun, 3 May 2026 15:43:25 -0300 Subject: [PATCH 1/9] feat(java): raise max payload size to 1MB --- .../src/integTest/java/com/rollbar/notifier/RollbarITest.java | 2 +- .../src/main/java/com/rollbar/notifier/RollbarBase.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rollbar-java/src/integTest/java/com/rollbar/notifier/RollbarITest.java b/rollbar-java/src/integTest/java/com/rollbar/notifier/RollbarITest.java index 11d7e162..59364d2c 100644 --- a/rollbar-java/src/integTest/java/com/rollbar/notifier/RollbarITest.java +++ b/rollbar-java/src/integTest/java/com/rollbar/notifier/RollbarITest.java @@ -352,7 +352,7 @@ public void ifPayloadIsTooLargeItShouldBeTruncated() { assertThat(frames, hasSize(20)); assertThat(PayloadTruncator.sizeInBytes(payloadJsonString), - lessThanOrEqualTo(512 * 1024)); + lessThanOrEqualTo(1024 * 1024)); } protected Sender buildSender(String url, String accessToken, Proxy proxy) { diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/RollbarBase.java b/rollbar-java/src/main/java/com/rollbar/notifier/RollbarBase.java index 6e1be6f2..ca5f45ec 100644 --- a/rollbar-java/src/main/java/com/rollbar/notifier/RollbarBase.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/RollbarBase.java @@ -36,7 +36,7 @@ public abstract class RollbarBase { private static final Logger LOGGER = LoggerFactory.getLogger(RollbarBase.class); - private static final int MAX_PAYLOAD_SIZE_BYTES = 512 * 1024; // 512kb + private static final int MAX_PAYLOAD_SIZE_BYTES = 1024 * 1024; // 1mb protected BodyFactory bodyFactory; protected PayloadTruncator payloadTruncator; From fbaec18c7a5afafd55e7f611997eb1a32ab7e262 Mon Sep 17 00:00:00 2001 From: buongarzoni Date: Sun, 3 May 2026 15:45:07 -0300 Subject: [PATCH 2/9] feat(java): compress outgoing payloads with gzip by default --- .../rollbar/notifier/config/CommonConfig.java | 9 ++++ .../notifier/config/ConfigBuilder.java | 27 +++++++++- .../rollbar/notifier/sender/SyncSender.java | 23 +++++++- .../notifier/sender/SyncSenderTest.java | 54 +++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/config/CommonConfig.java b/rollbar-java/src/main/java/com/rollbar/notifier/config/CommonConfig.java index f2fac03c..7fc39840 100644 --- a/rollbar-java/src/main/java/com/rollbar/notifier/config/CommonConfig.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/config/CommonConfig.java @@ -212,6 +212,15 @@ public interface CommonConfig { */ boolean truncateLargePayloads(); + /** + *

+ * If set to true (the default), payloads are gzip-compressed before sending. + * Set to false to send uncompressed JSON. + *

+ * @return true to compress payloads, false otherwise. + */ + boolean compressPayload(); + int maximumTelemetryData(); TelemetryEventTracker telemetryEventTracker(); diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/config/ConfigBuilder.java b/rollbar-java/src/main/java/com/rollbar/notifier/config/ConfigBuilder.java index 88f56b53..d91e3705 100644 --- a/rollbar-java/src/main/java/com/rollbar/notifier/config/ConfigBuilder.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/config/ConfigBuilder.java @@ -87,6 +87,8 @@ public class ConfigBuilder { protected boolean truncateLargePayloads; + protected boolean compressPayload; + private int maximumTelemetryData = RollbarTelemetryEventTracker.MAXIMUM_CAPACITY_FOR_TELEMETRY_EVENTS; @@ -101,6 +103,7 @@ protected ConfigBuilder(String accessToken) { this.accessToken = accessToken; this.handleUncaughtErrors = true; this.enabled = true; + this.compressPayload = true; this.defaultLevels = new DefaultLevels(); } @@ -136,6 +139,7 @@ private ConfigBuilder(Config config) { this.appPackages = config.appPackages(); this.defaultLevels = new DefaultLevels(config); this.truncateLargePayloads = config.truncateLargePayloads(); + this.compressPayload = config.compressPayload(); this.maximumTelemetryData = config.maximumTelemetryData(); this.telemetryEventTracker = config.telemetryEventTracker(); } @@ -480,6 +484,18 @@ public ConfigBuilder truncateLargePayloads(boolean truncate) { return this; } + /** + *

+ * If set to false, payloads will not be gzip-compressed before sending. Default: true. + *

+ * @param compress true to gzip-compress payloads. + * @return the builder instance. + */ + public ConfigBuilder compressPayload(boolean compress) { + this.compressPayload = compress; + return this; + } + /** *

* Maximum Telemetry events sent in a payload, only for the default TelemetryEventTracker, if @@ -526,7 +542,8 @@ public Config build() { SyncSender.Builder innerSender = new SyncSender.Builder(this.endpoint) .accessToken(accessToken) - .proxy(proxy); + .proxy(proxy) + .compressPayload(this.compressPayload); if (this.jsonSerializer != null) { innerSender.jsonSerializer(this.jsonSerializer); } @@ -601,6 +618,8 @@ private static class ConfigImpl implements Config { private final boolean truncateLargePayloads; + private final boolean compressPayload; + private final int maximumTelemetryData; private final TelemetryEventTracker telemetryEventTracker; @@ -637,6 +656,7 @@ private static class ConfigImpl implements Config { this.enabled = builder.enabled; this.defaultLevels = builder.defaultLevels; this.truncateLargePayloads = builder.truncateLargePayloads; + this.compressPayload = builder.compressPayload; this.maximumTelemetryData = builder.maximumTelemetryData; this.telemetryEventTracker = builder.telemetryEventTracker; } @@ -786,6 +806,11 @@ public boolean truncateLargePayloads() { return this.truncateLargePayloads; } + @Override + public boolean compressPayload() { + return this.compressPayload; + } + @Override public int maximumTelemetryData() { return this.maximumTelemetryData; diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java b/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java index cb5017eb..2f9db3c5 100755 --- a/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java @@ -12,6 +12,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; +import java.util.zip.GZIPOutputStream; import java.net.MalformedURLException; import java.net.Proxy; import java.net.URL; @@ -33,11 +34,14 @@ public class SyncSender extends AbstractSender { private final Proxy proxy; + private final boolean compressPayload; + SyncSender(Builder builder) { this.url = builder.url; this.jsonSerializer = builder.jsonSerializer; this.accessToken = builder.accessToken; this.proxy = builder.proxy != null ? builder.proxy : Proxy.NO_PROXY; + this.compressPayload = builder.compressPayload; } @Override @@ -69,6 +73,9 @@ private HttpURLConnection getConnection() throws IOException { connection.setRequestProperty("Accept-Charset", UTF_8); connection.setRequestProperty("Content-Type", "application/json; charset=" + UTF_8); connection.setRequestProperty("Accept", "application/json"); + if (compressPayload) { + connection.setRequestProperty("Content-Encoding", "gzip"); + } connection.setDoOutput(true); connection.setRequestMethod("POST"); @@ -78,7 +85,9 @@ private HttpURLConnection getConnection() throws IOException { private void sendJson(HttpURLConnection connection, byte[] bytes) throws IOException { OutputStream out = null; try { - out = connection.getOutputStream(); + out = compressPayload + ? new GZIPOutputStream(connection.getOutputStream()) + : connection.getOutputStream(); out.write(bytes, 0, bytes.length); } catch (IOException e) { throw e; @@ -131,6 +140,8 @@ public static final class Builder { private Proxy proxy; + private boolean compressPayload = true; + public Builder() { this(DEFAULT_API_ENDPOINT); } @@ -195,6 +206,16 @@ public Builder proxy(Proxy proxy) { return this; } + /** + * Whether to gzip-compress payloads before sending. Default: true. + * @param compress true to enable compression. + * @return the builder instance. + */ + public Builder compressPayload(boolean compress) { + this.compressPayload = compress; + return this; + } + /** * Builds the {@link SyncSender sync sender}. * diff --git a/rollbar-java/src/test/java/com/rollbar/notifier/sender/SyncSenderTest.java b/rollbar-java/src/test/java/com/rollbar/notifier/sender/SyncSenderTest.java index 70736995..4e4fcea6 100644 --- a/rollbar-java/src/test/java/com/rollbar/notifier/sender/SyncSenderTest.java +++ b/rollbar-java/src/test/java/com/rollbar/notifier/sender/SyncSenderTest.java @@ -17,9 +17,11 @@ import com.rollbar.notifier.sender.result.Response; import com.rollbar.notifier.sender.result.Result; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.zip.GZIPInputStream; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URL; @@ -76,6 +78,7 @@ public void setUp()throws Exception { sut = new SyncSender.Builder() .url(url) .jsonSerializer(serializer) + .compressPayload(false) .build(); sut.addListener(listener); } @@ -198,6 +201,57 @@ public void shouldSendThePayloadUsingAProxyIfProvided() throws Exception { verify(listener).onResponse(payload, expectedResponse); } + @Test + public void shouldSendGzipEncodedPayloadWhenCompressionEnabled() throws Exception { + ByteArrayOutputStream capturedBytes = new ByteArrayOutputStream(); + when(url.openConnection(eq(Proxy.NO_PROXY))).thenReturn(connection); + when(connection.getOutputStream()).thenReturn(capturedBytes); + + int responseCode = 200; + String responseJson = "simulated_response_json"; + when(connection.getResponseCode()).thenReturn(responseCode); + when(connection.getInputStream()) + .thenReturn(new ByteArrayInputStream(responseJson.getBytes(UTF_8))); + when(serializer.resultFrom(responseJson)).thenReturn(result); + + SyncSender compressingSut = new SyncSender.Builder() + .url(url) + .jsonSerializer(serializer) + .compressPayload(true) + .build(); + + compressingSut.send(payload); + + verify(connection).setRequestProperty("Content-Encoding", "gzip"); + + GZIPInputStream gzipIn = new GZIPInputStream( + new ByteArrayInputStream(capturedBytes.toByteArray())); + ByteArrayOutputStream decompressedBytes = new ByteArrayOutputStream(); + byte[] buf = new byte[1024]; + int n; + while ((n = gzipIn.read(buf)) != -1) { + decompressedBytes.write(buf, 0, n); + } + String decompressed = decompressedBytes.toString(UTF_8); + assertThat(decompressed, is(PAYLOAD_JSON)); + } + + @Test + public void shouldNotSetContentEncodingWhenCompressionDisabled() throws Exception { + int responseCode = 200; + String responseJson = "simulated_response_json"; + when(connection.getResponseCode()).thenReturn(responseCode); + when(connection.getInputStream()) + .thenReturn(new ByteArrayInputStream(responseJson.getBytes(UTF_8))); + when(serializer.resultFrom(responseJson)).thenReturn(result); + + sut.send(payload); + + verify(connection, org.mockito.Mockito.never()) + .setRequestProperty(org.mockito.ArgumentMatchers.eq("Content-Encoding"), + org.mockito.ArgumentMatchers.anyString()); + } + private void verifyHttp() throws Exception { verify(connection).setRequestProperty("Accept-Charset", UTF_8); verify(connection).setRequestProperty("Content-Type", "application/json; charset=" + UTF_8); From 70a487420b4732bc00938d1feee348bb51af3ed7 Mon Sep 17 00:00:00 2001 From: buongarzoni Date: Sun, 3 May 2026 15:52:35 -0300 Subject: [PATCH 3/9] style(java): fix import order in SyncSender --- .../src/main/java/com/rollbar/notifier/sender/SyncSender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java b/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java index 2f9db3c5..314033c4 100755 --- a/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java @@ -12,10 +12,10 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; -import java.util.zip.GZIPOutputStream; import java.net.MalformedURLException; import java.net.Proxy; import java.net.URL; +import java.util.zip.GZIPOutputStream; /** * Synchronous implementation of the {@link Sender sender}. From deff3cba84d358269d06b869f4406b6bf0401e8e Mon Sep 17 00:00:00 2001 From: buongarzoni Date: Sun, 3 May 2026 16:07:37 -0300 Subject: [PATCH 4/9] fix(java): add default implementation to compressPayload to avoid API break --- .../main/java/com/rollbar/notifier/config/CommonConfig.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/config/CommonConfig.java b/rollbar-java/src/main/java/com/rollbar/notifier/config/CommonConfig.java index 7fc39840..c4c4ea41 100644 --- a/rollbar-java/src/main/java/com/rollbar/notifier/config/CommonConfig.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/config/CommonConfig.java @@ -219,7 +219,9 @@ public interface CommonConfig { *

* @return true to compress payloads, false otherwise. */ - boolean compressPayload(); + default boolean compressPayload() { + return true; + } int maximumTelemetryData(); From 44f288926c8b5f3e88582cfcccce51a46ab39292 Mon Sep 17 00:00:00 2001 From: buongarzoni Date: Sun, 3 May 2026 16:35:29 -0300 Subject: [PATCH 5/9] feat(reactive-streams): wire gzip compression into AsyncSender matching SyncSender behaviour --- .../sender/http/ReactorAsyncHttpClient.java | 6 ++- .../notifier/config/ConfigBuilder.java | 25 +++++++++++- .../notifier/sender/AsyncSender.java | 40 +++++++++++++++++-- .../sender/http/ApacheRequestPublisher.java | 6 ++- .../sender/http/AsyncHttpRequest.java | 9 +++++ .../sender/http/AsyncHttpRequestImpl.java | 22 ++++++---- 6 files changed, 95 insertions(+), 13 deletions(-) diff --git a/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java b/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java index 4d0d6855..3109d4f2 100644 --- a/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java +++ b/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java @@ -47,7 +47,11 @@ public class ReactorAsyncHttpClient implements AsyncHttpClient { public Publisher send(AsyncHttpRequest httpRequest) { ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); - buffer.writeCharSequence(httpRequest.getBody(), StandardCharsets.UTF_8); + if (httpRequest.getBodyBytes() != null) { + buffer.writeBytes(httpRequest.getBodyBytes()); + } else { + buffer.writeCharSequence(httpRequest.getBody(), StandardCharsets.UTF_8); + } Mono buf = Mono.just(buffer); return httpClient diff --git a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/config/ConfigBuilder.java b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/config/ConfigBuilder.java index 48536116..5151e234 100644 --- a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/config/ConfigBuilder.java +++ b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/config/ConfigBuilder.java @@ -61,6 +61,7 @@ public final class ConfigBuilder { private boolean enabled; private DefaultLevels defaultLevels; private boolean truncateLargePayloads; + private boolean compressPayload; private int maximumTelemetryData = RollbarTelemetryEventTracker.MAXIMUM_CAPACITY_FOR_TELEMETRY_EVENTS; private TelemetryEventTracker telemetryEventTracker; @@ -74,6 +75,7 @@ protected ConfigBuilder(String accessToken) { this.accessToken = accessToken; this.handleUncaughtErrors = true; this.enabled = true; + this.compressPayload = true; this.defaultLevels = new DefaultLevels(); } @@ -106,6 +108,7 @@ private ConfigBuilder(Config config) { this.appPackages = config.appPackages(); this.defaultLevels = new DefaultLevels(config); this.truncateLargePayloads = config.truncateLargePayloads(); + this.compressPayload = config.compressPayload(); this.maximumTelemetryData = config.maximumTelemetryData(); this.telemetryEventTracker = config.telemetryEventTracker(); } @@ -469,6 +472,18 @@ public ConfigBuilder truncateLargePayloads(boolean truncate) { return this; } + /** + *

+ * If set to false, payloads will not be gzip-compressed before sending. Default: true. + *

+ * @param compress true to gzip-compress payloads. + * @return the builder instance. + */ + public ConfigBuilder compressPayload(boolean compress) { + this.compressPayload = compress; + return this; + } + /** *

* Maximum Telemetry events sent in a payload, only for the default TelemetryEventTracker, if @@ -517,7 +532,8 @@ public Config build() { httpClient = AsyncHttpClientFactory.defaultClient(); } AsyncSender.Builder senderBuilder = new AsyncSender.Builder(httpClient, this.endpoint) - .accessToken(accessToken); + .accessToken(accessToken) + .compressPayload(this.compressPayload); if (this.jsonSerializer != null) { senderBuilder.jsonSerializer(this.jsonSerializer); @@ -565,6 +581,7 @@ private static class ConfigImpl implements Config { private final DefaultLevels defaultLevels; private final JsonSerializer jsonSerializer; private final boolean truncateLargePayloads; + private final boolean compressPayload; private final int maximumTelemetryData; private final TelemetryEventTracker telemetryEventTracker; @@ -599,6 +616,7 @@ private static class ConfigImpl implements Config { this.defaultLevels = builder.defaultLevels; this.jsonSerializer = builder.jsonSerializer; this.truncateLargePayloads = builder.truncateLargePayloads; + this.compressPayload = builder.compressPayload; this.maximumTelemetryData = builder.maximumTelemetryData; this.telemetryEventTracker = builder.telemetryEventTracker; } @@ -743,6 +761,11 @@ public boolean truncateLargePayloads() { return truncateLargePayloads; } + @Override + public boolean compressPayload() { + return compressPayload; + } + @Override public int maximumTelemetryData() { return this.maximumTelemetryData; diff --git a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/AsyncSender.java b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/AsyncSender.java index 06799c94..df9e8d34 100644 --- a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/AsyncSender.java +++ b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/AsyncSender.java @@ -10,9 +10,12 @@ import com.rollbar.reactivestreams.notifier.sender.http.AsyncHttpClient; import com.rollbar.reactivestreams.notifier.sender.http.AsyncHttpRequest; import com.rollbar.reactivestreams.notifier.sender.http.AsyncHttpResponse; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.LinkedHashMap; +import java.util.zip.GZIPOutputStream; import org.reactivestreams.Publisher; /** @@ -23,12 +26,14 @@ public class AsyncSender implements Sender { private final String url; private final JsonSerializer jsonSerializer; private final String accessToken; + private final boolean compressPayload; AsyncSender(Builder builder) { this.httpClient = builder.httpClient; this.url = builder.url.toExternalForm(); this.jsonSerializer = builder.jsonSerializer; this.accessToken = builder.accessToken; + this.compressPayload = builder.compressPayload; } /** @@ -49,10 +54,15 @@ public Publisher send(Payload payload) { headers.put("Content-Type", "application/json; charset=" + SyncSender.UTF_8); headers.put("Accept", "application/json"); - String reqBody = jsonSerializer.toJson(payload); + String json = jsonSerializer.toJson(payload); - AsyncHttpRequest request = - AsyncHttpRequest.Builder.build(this.url, headers.entrySet(), reqBody); + AsyncHttpRequest request; + if (compressPayload) { + headers.put("Content-Encoding", "gzip"); + request = AsyncHttpRequest.Builder.build(this.url, headers.entrySet(), compress(json)); + } else { + request = AsyncHttpRequest.Builder.build(this.url, headers.entrySet(), json); + } return Utils.map(httpClient.send(request), new Utils.Converter() { @@ -64,6 +74,18 @@ public Response convert(AsyncHttpResponse from) { }); } + private static byte[] compress(String json) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(baos); + gzip.write(json.getBytes(SyncSender.UTF_8)); + gzip.close(); + return baos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Failed to gzip-compress payload", e); + } + } + @Override public void close(boolean wait) { httpClient.close(wait); @@ -82,6 +104,7 @@ public static class Builder { private URL url; private JsonSerializer jsonSerializer; private String accessToken; + private boolean compressPayload = true; /** * Constructor. @@ -148,6 +171,17 @@ public Builder accessToken(String accessToken) { return this; } + /** + * Whether to gzip-compress payloads before sending. Default: true. + * + * @param compress true to enable compression. + * @return the builder instance. + */ + public Builder compressPayload(boolean compress) { + this.compressPayload = compress; + return this; + } + /** * Builds the {@link AsyncSender} async sender. * diff --git a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java index 6edb2c3c..bc46b0ee 100644 --- a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java +++ b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java @@ -102,7 +102,11 @@ private SimpleRequestProducer buildRequest() { req.setHeader(header.getKey(), header.getValue()); } - req.setBody(request.getBody(), ContentType.APPLICATION_JSON); + if (request.getBodyBytes() != null) { + req.setBody(request.getBodyBytes(), ContentType.APPLICATION_JSON); + } else { + req.setBody(request.getBody(), ContentType.APPLICATION_JSON); + } return SimpleRequestProducer.create(req); } diff --git a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequest.java b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequest.java index a9ab308f..13f4c0f6 100644 --- a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequest.java +++ b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequest.java @@ -13,10 +13,19 @@ public interface AsyncHttpRequest { String getBody(); + default byte[] getBodyBytes() { + return null; + } + class Builder { public static AsyncHttpRequest build(String url, Set> headers, String reqBody) { return new AsyncHttpRequestImpl(url, headers, reqBody); } + + public static AsyncHttpRequest build(String url, Set> headers, + byte[] body) { + return new AsyncHttpRequestImpl(url, headers, body); + } } } diff --git a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequestImpl.java b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequestImpl.java index 246eb752..095df482 100644 --- a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequestImpl.java +++ b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequestImpl.java @@ -9,19 +9,22 @@ class AsyncHttpRequestImpl implements AsyncHttpRequest { private final String url; private final Iterable> headers; private final String body; + private final byte[] bodyBytes; - /** - * Constructor. - * - * @param url The URL to connect to. - * @param headers Request headers. - * @param body Request body. - */ public AsyncHttpRequestImpl(String url, Iterable> headers, String body) { this.url = url; this.headers = headers; this.body = body; + this.bodyBytes = null; + } + + public AsyncHttpRequestImpl(String url, Iterable> headers, + byte[] bodyBytes) { + this.url = url; + this.headers = headers; + this.body = null; + this.bodyBytes = bodyBytes; } @Override @@ -38,4 +41,9 @@ public Iterable> getHeaders() { public String getBody() { return body; } + + @Override + public byte[] getBodyBytes() { + return bodyBytes; + } } From be1e055dc7cd2cf7348ae1665a72bf5b130a74c6 Mon Sep 17 00:00:00 2001 From: buongarzoni Date: Sun, 3 May 2026 16:46:48 -0300 Subject: [PATCH 6/9] fix(java): close raw stream if GZIPOutputStream constructor throws in sendJson --- .../java/com/rollbar/notifier/sender/SyncSender.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java b/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java index 314033c4..2a8f0cb3 100755 --- a/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/sender/SyncSender.java @@ -85,12 +85,14 @@ private HttpURLConnection getConnection() throws IOException { private void sendJson(HttpURLConnection connection, byte[] bytes) throws IOException { OutputStream out = null; try { - out = compressPayload - ? new GZIPOutputStream(connection.getOutputStream()) - : connection.getOutputStream(); + OutputStream raw = connection.getOutputStream(); + try { + out = compressPayload ? new GZIPOutputStream(raw) : raw; + } catch (IOException e) { + ObjectsUtils.close(raw); + throw e; + } out.write(bytes, 0, bytes.length); - } catch (IOException e) { - throw e; } finally { ObjectsUtils.close(out); } From 05a7d750e1f7820723897933a7529c2eadf79822 Mon Sep 17 00:00:00 2001 From: buongarzoni Date: Sun, 3 May 2026 16:59:46 -0300 Subject: [PATCH 7/9] fix(reactive-streams): disable compression in AsyncSenderTest to avoid null getBody() --- .../rollbar/reactivestreams/notifier/sender/AsyncSenderTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rollbar-reactive-streams/src/test/java/com/rollbar/reactivestreams/notifier/sender/AsyncSenderTest.java b/rollbar-reactive-streams/src/test/java/com/rollbar/reactivestreams/notifier/sender/AsyncSenderTest.java index e1e24f52..66cd99b0 100644 --- a/rollbar-reactive-streams/src/test/java/com/rollbar/reactivestreams/notifier/sender/AsyncSenderTest.java +++ b/rollbar-reactive-streams/src/test/java/com/rollbar/reactivestreams/notifier/sender/AsyncSenderTest.java @@ -52,6 +52,7 @@ public void setUp() { sender = new AsyncSender.Builder(httpClient) .accessToken(ACCESS_TOKEN) + .compressPayload(false) .build(); } From 1f5f214e0f51ed4495f716e6709c84f9e0531ca8 Mon Sep 17 00:00:00 2001 From: buongarzoni Date: Mon, 4 May 2026 02:38:53 -0300 Subject: [PATCH 8/9] refactor(reactive-streams): use compression-intent pattern for safe third-party extensibility --- .../sender/http/ReactorAsyncHttpClient.java | 23 ++++++++++++++++-- .../notifier/sender/AsyncSender.java | 24 ++----------------- .../sender/http/ApacheRequestPublisher.java | 21 ++++++++++++++-- .../sender/http/AsyncHttpRequest.java | 10 ++++---- .../sender/http/AsyncHttpRequestImpl.java | 18 ++++---------- .../sender/http/ApacheAsyncHttpTckTest.java | 4 ++-- 6 files changed, 54 insertions(+), 46 deletions(-) diff --git a/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java b/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java index 3109d4f2..d075f3f9 100644 --- a/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java +++ b/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java @@ -1,12 +1,16 @@ package com.rollbar.reactivestreams.notifier.sender.http; +import com.rollbar.notifier.sender.SyncSender; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; import java.nio.charset.StandardCharsets; import java.util.AbstractMap; import java.util.Map; +import java.util.zip.GZIPOutputStream; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -47,8 +51,8 @@ public class ReactorAsyncHttpClient implements AsyncHttpClient { public Publisher send(AsyncHttpRequest httpRequest) { ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); - if (httpRequest.getBodyBytes() != null) { - buffer.writeBytes(httpRequest.getBodyBytes()); + if (httpRequest.isCompressionRequested()) { + buffer.writeBytes(compress(httpRequest.getBody())); } else { buffer.writeCharSequence(httpRequest.getBody(), StandardCharsets.UTF_8); } @@ -59,6 +63,9 @@ public Publisher send(AsyncHttpRequest httpRequest) { for (Map.Entry header : httpRequest.getHeaders()) { entries.add(header.getKey(), header.getValue()); } + if (httpRequest.isCompressionRequested()) { + entries.add("Content-Encoding", "gzip"); + } }) .post() .uri(httpRequest.getUrl()) @@ -144,6 +151,18 @@ private static ProxyProvider.Proxy getProxyType(Proxy proxy) { } } + private static byte[] compress(String json) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(baos); + gzip.write(json.getBytes(SyncSender.UTF_8)); + gzip.close(); + return baos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Failed to gzip-compress payload", e); + } + } + public static final class Builder { private Proxy proxy; private ConnectionProvider connectionProvider; diff --git a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/AsyncSender.java b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/AsyncSender.java index df9e8d34..45e96dd4 100644 --- a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/AsyncSender.java +++ b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/AsyncSender.java @@ -10,12 +10,9 @@ import com.rollbar.reactivestreams.notifier.sender.http.AsyncHttpClient; import com.rollbar.reactivestreams.notifier.sender.http.AsyncHttpRequest; import com.rollbar.reactivestreams.notifier.sender.http.AsyncHttpResponse; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.LinkedHashMap; -import java.util.zip.GZIPOutputStream; import org.reactivestreams.Publisher; /** @@ -56,13 +53,8 @@ public Publisher send(Payload payload) { String json = jsonSerializer.toJson(payload); - AsyncHttpRequest request; - if (compressPayload) { - headers.put("Content-Encoding", "gzip"); - request = AsyncHttpRequest.Builder.build(this.url, headers.entrySet(), compress(json)); - } else { - request = AsyncHttpRequest.Builder.build(this.url, headers.entrySet(), json); - } + AsyncHttpRequest request = + AsyncHttpRequest.Builder.build(this.url, headers.entrySet(), json, compressPayload); return Utils.map(httpClient.send(request), new Utils.Converter() { @@ -74,18 +66,6 @@ public Response convert(AsyncHttpResponse from) { }); } - private static byte[] compress(String json) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - GZIPOutputStream gzip = new GZIPOutputStream(baos); - gzip.write(json.getBytes(SyncSender.UTF_8)); - gzip.close(); - return baos.toByteArray(); - } catch (IOException e) { - throw new RuntimeException("Failed to gzip-compress payload", e); - } - } - @Override public void close(boolean wait) { httpClient.close(wait); diff --git a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java index bc46b0ee..5a56a3ce 100644 --- a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java +++ b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java @@ -1,10 +1,14 @@ package com.rollbar.reactivestreams.notifier.sender.http; +import com.rollbar.notifier.sender.SyncSender; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.net.URI; import java.util.Map; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.zip.GZIPOutputStream; import org.apache.hc.client5.http.async.HttpAsyncClient; import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; @@ -102,8 +106,9 @@ private SimpleRequestProducer buildRequest() { req.setHeader(header.getKey(), header.getValue()); } - if (request.getBodyBytes() != null) { - req.setBody(request.getBodyBytes(), ContentType.APPLICATION_JSON); + if (request.isCompressionRequested()) { + req.addHeader("Content-Encoding", "gzip"); + req.setBody(compress(request.getBody()), ContentType.APPLICATION_JSON); } else { req.setBody(request.getBody(), ContentType.APPLICATION_JSON); } @@ -120,4 +125,16 @@ public void cancel() { } } } + + private static byte[] compress(String json) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(baos); + gzip.write(json.getBytes(SyncSender.UTF_8)); + gzip.close(); + return baos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Failed to gzip-compress payload", e); + } + } } diff --git a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequest.java b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequest.java index 13f4c0f6..6827e526 100644 --- a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequest.java +++ b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequest.java @@ -13,19 +13,19 @@ public interface AsyncHttpRequest { String getBody(); - default byte[] getBodyBytes() { - return null; + default boolean isCompressionRequested() { + return false; } class Builder { public static AsyncHttpRequest build(String url, Set> headers, String reqBody) { - return new AsyncHttpRequestImpl(url, headers, reqBody); + return new AsyncHttpRequestImpl(url, headers, reqBody, false); } public static AsyncHttpRequest build(String url, Set> headers, - byte[] body) { - return new AsyncHttpRequestImpl(url, headers, body); + String reqBody, boolean compressionRequested) { + return new AsyncHttpRequestImpl(url, headers, reqBody, compressionRequested); } } } diff --git a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequestImpl.java b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequestImpl.java index 095df482..5454b773 100644 --- a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequestImpl.java +++ b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/AsyncHttpRequestImpl.java @@ -9,22 +9,14 @@ class AsyncHttpRequestImpl implements AsyncHttpRequest { private final String url; private final Iterable> headers; private final String body; - private final byte[] bodyBytes; + private final boolean compressionRequested; public AsyncHttpRequestImpl(String url, Iterable> headers, - String body) { + String body, boolean compressionRequested) { this.url = url; this.headers = headers; this.body = body; - this.bodyBytes = null; - } - - public AsyncHttpRequestImpl(String url, Iterable> headers, - byte[] bodyBytes) { - this.url = url; - this.headers = headers; - this.body = null; - this.bodyBytes = bodyBytes; + this.compressionRequested = compressionRequested; } @Override @@ -43,7 +35,7 @@ public String getBody() { } @Override - public byte[] getBodyBytes() { - return bodyBytes; + public boolean isCompressionRequested() { + return compressionRequested; } } diff --git a/rollbar-reactive-streams/src/test/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheAsyncHttpTckTest.java b/rollbar-reactive-streams/src/test/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheAsyncHttpTckTest.java index 50b0e05a..9c416d9f 100644 --- a/rollbar-reactive-streams/src/test/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheAsyncHttpTckTest.java +++ b/rollbar-reactive-streams/src/test/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheAsyncHttpTckTest.java @@ -74,7 +74,7 @@ public Publisher createPublisher(long elements) { }); return new ApacheRequestPublisher(client, new AsyncHttpRequestImpl(url, - new LinkedHashMap().entrySet(), "")); + new LinkedHashMap().entrySet(), "", false)); } @Override @@ -85,7 +85,7 @@ public Publisher createFailedPublisher() { }); return new ApacheRequestPublisher(client, new AsyncHttpRequestImpl(url, - new LinkedHashMap().entrySet(), "")); + new LinkedHashMap().entrySet(), "", false)); } @Override From 9541f0a1ceb321a0cf0ef6936035d79e63da5e2f Mon Sep 17 00:00:00 2001 From: buongarzoni Date: Mon, 4 May 2026 03:06:38 -0300 Subject: [PATCH 9/9] fix(reactive-streams): prevent GZIPOutputStream and ByteBuf resource leaks --- .../sender/http/ReactorAsyncHttpClient.java | 19 ++++++++++++------- .../sender/http/ApacheRequestPublisher.java | 6 +++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java b/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java index d075f3f9..a283ff92 100644 --- a/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java +++ b/rollbar-reactive-streams-reactor/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ReactorAsyncHttpClient.java @@ -51,10 +51,15 @@ public class ReactorAsyncHttpClient implements AsyncHttpClient { public Publisher send(AsyncHttpRequest httpRequest) { ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); - if (httpRequest.isCompressionRequested()) { - buffer.writeBytes(compress(httpRequest.getBody())); - } else { - buffer.writeCharSequence(httpRequest.getBody(), StandardCharsets.UTF_8); + try { + if (httpRequest.isCompressionRequested()) { + buffer.writeBytes(compress(httpRequest.getBody())); + } else { + buffer.writeCharSequence(httpRequest.getBody(), StandardCharsets.UTF_8); + } + } catch (Throwable t) { + buffer.release(); + throw t; } Mono buf = Mono.just(buffer); @@ -154,9 +159,9 @@ private static ProxyProvider.Proxy getProxyType(Proxy proxy) { private static byte[] compress(String json) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - GZIPOutputStream gzip = new GZIPOutputStream(baos); - gzip.write(json.getBytes(SyncSender.UTF_8)); - gzip.close(); + try (GZIPOutputStream gzip = new GZIPOutputStream(baos)) { + gzip.write(json.getBytes(SyncSender.UTF_8)); + } return baos.toByteArray(); } catch (IOException e) { throw new RuntimeException("Failed to gzip-compress payload", e); diff --git a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java index 5a56a3ce..e7470313 100644 --- a/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java +++ b/rollbar-reactive-streams/src/main/java/com/rollbar/reactivestreams/notifier/sender/http/ApacheRequestPublisher.java @@ -129,9 +129,9 @@ public void cancel() { private static byte[] compress(String json) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - GZIPOutputStream gzip = new GZIPOutputStream(baos); - gzip.write(json.getBytes(SyncSender.UTF_8)); - gzip.close(); + try (GZIPOutputStream gzip = new GZIPOutputStream(baos)) { + gzip.write(json.getBytes(SyncSender.UTF_8)); + } return baos.toByteArray(); } catch (IOException e) { throw new RuntimeException("Failed to gzip-compress payload", e);