From ef90ccbf84627c7c6cc8a38f92a53bd0bf044383 Mon Sep 17 00:00:00 2001 From: Aditya Kurkure Date: Tue, 28 Jan 2025 10:57:48 +0530 Subject: [PATCH] feat: Enable GZIP compression for request payloads Added support for GZIP compression in HTTP requests to improve payload handling efficiency. Introduced configuration options including methods in `MixpanelAPI` and `MPConfig` for enabling or disabling GZIP compression. Updated `HttpService` to conditionally handle GZIP-compressed payloads and included corresponding tests to ensure functionality. --- .../android/mpmetrics/MPConfigTest.java | 26 +++++++++++++++ .../android/mpmetrics/AnalyticsMessages.java | 2 +- .../mixpanel/android/mpmetrics/MPConfig.java | 6 ++++ .../android/mpmetrics/MixpanelAPI.java | 18 ++++++++++ .../mixpanel/android/util/HttpService.java | 33 ++++++++++++++++--- 5 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/androidTest/java/com/mixpanel/android/mpmetrics/MPConfigTest.java b/src/androidTest/java/com/mixpanel/android/mpmetrics/MPConfigTest.java index 56c8896c..1852a8ad 100644 --- a/src/androidTest/java/com/mixpanel/android/mpmetrics/MPConfigTest.java +++ b/src/androidTest/java/com/mixpanel/android/mpmetrics/MPConfigTest.java @@ -195,6 +195,32 @@ public void testSetMaximumDatabaseLimit2() { assertEquals(100000000, mixpanelAPI.getMaximumDatabaseLimit()); } + @Test + public void testShouldGzipRequestPayload() { + final Bundle metaData = new Bundle(); + metaData.putBoolean("com.mixpanel.android.MPConfig.GzipRequestPayload", true); + MPConfig mpConfig = mpConfig(metaData); + assertTrue(mpConfig.shouldGzipRequestPayload()); + + mpConfig.setShouldGzipRequestPayload(false); + assertFalse(mpConfig.shouldGzipRequestPayload()); + + mpConfig.setShouldGzipRequestPayload(true); + assertTrue(mpConfig.shouldGzipRequestPayload()); + + // assert false by default + MPConfig mpConfig2 = mpConfig(new Bundle()); + assertFalse(mpConfig2.shouldGzipRequestPayload()); + + MixpanelAPI mixpanelAPI = mixpanelApi(mpConfig); + + assertTrue(mixpanelAPI.shouldGzipRequestPayload()); + + mixpanelAPI.setShouldGzipRequestPayload(false); + assertFalse(mixpanelAPI.shouldGzipRequestPayload()); + + } + private MPConfig mpConfig(final Bundle metaData) { return new MPConfig(metaData, InstrumentationRegistry.getInstrumentation().getContext(), null); } diff --git a/src/main/java/com/mixpanel/android/mpmetrics/AnalyticsMessages.java b/src/main/java/com/mixpanel/android/mpmetrics/AnalyticsMessages.java index aa6a67fa..267451e8 100644 --- a/src/main/java/com/mixpanel/android/mpmetrics/AnalyticsMessages.java +++ b/src/main/java/com/mixpanel/android/mpmetrics/AnalyticsMessages.java @@ -171,7 +171,7 @@ protected MPDbAdapter makeDbAdapter(Context context) { } protected RemoteService getPoster() { - return new HttpService(); + return new HttpService(mConfig.shouldGzipRequestPayload()); } //////////////////////////////////////////////////// diff --git a/src/main/java/com/mixpanel/android/mpmetrics/MPConfig.java b/src/main/java/com/mixpanel/android/mpmetrics/MPConfig.java index 91d5d86d..c15fed50 100644 --- a/src/main/java/com/mixpanel/android/mpmetrics/MPConfig.java +++ b/src/main/java/com/mixpanel/android/mpmetrics/MPConfig.java @@ -200,6 +200,7 @@ public synchronized void setOfflineMode(OfflineMode offlineMode) { mBulkUploadLimit = metaData.getInt("com.mixpanel.android.MPConfig.BulkUploadLimit", 40); // 40 records default mFlushInterval = metaData.getInt("com.mixpanel.android.MPConfig.FlushInterval", 60 * 1000); // one minute default mFlushBatchSize = metaData.getInt("com.mixpanel.android.MPConfig.FlushBatchSize", 50); // flush 50 events at a time by default + shouldGzipRequestPayload = metaData.getBoolean("com.mixpanel.android.MPConfig.GzipRequestPayload", false); mFlushOnBackground = metaData.getBoolean("com.mixpanel.android.MPConfig.FlushOnBackground", true); mMinimumDatabaseLimit = metaData.getInt("com.mixpanel.android.MPConfig.MinimumDatabaseLimit", 20 * 1024 * 1024); // 20 Mb mMaximumDatabaseLimit = metaData.getInt("com.mixpanel.android.MPConfig.MaximumDatabaseLimit", Integer.MAX_VALUE); // 2 Gb @@ -277,6 +278,10 @@ public int getFlushBatchSize() { public void setFlushBatchSize(int flushBatchSize) { mFlushBatchSize = flushBatchSize; } + public boolean shouldGzipRequestPayload() { return shouldGzipRequestPayload; } + + public void setShouldGzipRequestPayload(Boolean shouldGzip) { shouldGzipRequestPayload = shouldGzip; } + // Throw away records that are older than this in milliseconds. Should be below the server side age limit for events. public long getDataExpiration() { @@ -476,6 +481,7 @@ public String toString() { private String mPeopleEndpoint; private String mGroupsEndpoint; private int mFlushBatchSize; + private boolean shouldGzipRequestPayload; private final String mResourcePackageName; private final int mMinSessionDuration; diff --git a/src/main/java/com/mixpanel/android/mpmetrics/MixpanelAPI.java b/src/main/java/com/mixpanel/android/mpmetrics/MixpanelAPI.java index a603955c..59d7c227 100644 --- a/src/main/java/com/mixpanel/android/mpmetrics/MixpanelAPI.java +++ b/src/main/java/com/mixpanel/android/mpmetrics/MixpanelAPI.java @@ -513,6 +513,24 @@ public int getFlushBatchSize() { return mConfig.getFlushBatchSize(); } + /** + * Set whether the request payload should be GZIP-compressed before being sent. + * + * @param shouldGzipRequestPayload boolean, true to enable GZIP compression, false otherwise. + */ + public void setShouldGzipRequestPayload(boolean shouldGzipRequestPayload) { + mConfig.setShouldGzipRequestPayload(shouldGzipRequestPayload); + } + + /** + * Get whether the request payload is currently set to be GZIP-compressed. + * + * @return boolean, whether GZIP compression is enabled + */ + public boolean shouldGzipRequestPayload() { + return mConfig.shouldGzipRequestPayload(); + } + /** * Set an integer number of bytes, the maximum size limit to the Mixpanel database. * diff --git a/src/main/java/com/mixpanel/android/util/HttpService.java b/src/main/java/com/mixpanel/android/util/HttpService.java index e2336145..e924160b 100644 --- a/src/main/java/com/mixpanel/android/util/HttpService.java +++ b/src/main/java/com/mixpanel/android/util/HttpService.java @@ -18,6 +18,7 @@ import java.net.InetAddress; import java.net.URL; import java.util.Map; +import java.util.zip.GZIPOutputStream; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; @@ -27,10 +28,20 @@ */ public class HttpService implements RemoteService { + + private final boolean shouldGzipRequestPayload; + private static boolean sIsMixpanelBlocked; private static final int MIN_UNAVAILABLE_HTTP_RESPONSE_CODE = HttpURLConnection.HTTP_INTERNAL_ERROR; private static final int MAX_UNAVAILABLE_HTTP_RESPONSE_CODE = 599; + public HttpService(boolean shouldGzipRequestPayload) { + this.shouldGzipRequestPayload = shouldGzipRequestPayload; + } + + public HttpService() { + this(false); + } @Override public void checkIsMixpanelBlocked() { Thread t = new Thread(new Runnable() { @@ -104,7 +115,7 @@ public byte[] performRequest(String endpointUrl, ProxyServerInteractor interacto while (retries < 3 && !succeeded) { InputStream in = null; OutputStream out = null; - BufferedOutputStream bout = null; + OutputStream bout = null; HttpURLConnection connection = null; try { @@ -131,12 +142,15 @@ public byte[] performRequest(String endpointUrl, ProxyServerInteractor interacto builder.appendQueryParameter(param.getKey(), param.getValue().toString()); } String query = builder.build().getEncodedQuery(); - - connection.setFixedLengthStreamingMode(query.getBytes().length); + if (shouldGzipRequestPayload) { + connection.setRequestProperty(CONTENT_ENCODING_HEADER, GZIP_CONTENT_TYPE_HEADER); + } else { + connection.setFixedLengthStreamingMode(query.getBytes().length); + } connection.setDoOutput(true); connection.setRequestMethod("POST"); out = connection.getOutputStream(); - bout = new BufferedOutputStream(out); + bout = getBufferedOutputStream(out); bout.write(query.getBytes("UTF-8")); bout.flush(); bout.close(); @@ -179,6 +193,14 @@ public byte[] performRequest(String endpointUrl, ProxyServerInteractor interacto return response; } + private OutputStream getBufferedOutputStream(OutputStream out) throws IOException { + if(shouldGzipRequestPayload) { + return new GZIPOutputStream(new BufferedOutputStream(out), HTTP_OUTPUT_STREAM_BUFFER_SIZE); + } else { + return new BufferedOutputStream(out); + } + } + private static boolean isProxyRequest(String endpointUrl) { return !endpointUrl.toLowerCase().contains(MIXPANEL_API.toLowerCase()); } @@ -199,4 +221,7 @@ private static byte[] slurp(final InputStream inputStream) } private static final String LOGTAG = "MixpanelAPI.Message"; + private static final int HTTP_OUTPUT_STREAM_BUFFER_SIZE = 8192; + private static final String CONTENT_ENCODING_HEADER = "Content-Encoding"; + private static final String GZIP_CONTENT_TYPE_HEADER = "gzip"; }