From b07bfd04122ec27cd479e652307e6e55fb8aaffd Mon Sep 17 00:00:00 2001 From: Alexander Gavrishev Date: Mon, 12 Sep 2022 11:48:13 +0300 Subject: [PATCH 1/2] Android: Allow to provide a different HttpClient implementation --- lib/android_build/app/build.gradle | 4 ++ .../applications/events/HttpClient.java | 56 +++++++++++++------ .../events/HttpClientRequest.java | 31 ++++++++++ 3 files changed, 73 insertions(+), 18 deletions(-) create mode 100644 lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClientRequest.java diff --git a/lib/android_build/app/build.gradle b/lib/android_build/app/build.gradle index 750b05530..9ea52e535 100644 --- a/lib/android_build/app/build.gradle +++ b/lib/android_build/app/build.gradle @@ -30,6 +30,10 @@ android { version "3.10.2" } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { diff --git a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClient.java b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClient.java index 2fd9a21aa..eb815c2f5 100644 --- a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClient.java +++ b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClient.java @@ -23,10 +23,12 @@ import android.os.Build; import android.provider.Settings; import android.util.Log; + import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; + import java.io.BufferedInputStream; import java.io.OutputStream; import java.net.HttpURLConnection; @@ -34,6 +36,7 @@ import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -86,16 +89,15 @@ public final void onCapabilitiesChanged( private boolean m_metered; } -class Request implements Runnable { +class Request implements HttpClientRequest { Request( - HttpClient parent, - String url, - String method, - byte[] body, - String request_id, - int[] header_length, - byte[] header_buffer) + @NonNull HttpClient parent, + String url, + String method, + byte[] body, + String request_id, + @NonNull Map headers) throws java.io.IOException { m_parent = parent; m_connection = (HttpURLConnection) parent.newUrl(url).openConnection(); @@ -106,13 +108,8 @@ class Request implements Runnable { m_connection.setDoOutput(true); } m_request_id = request_id; - int offset = 0; - for (int i = 0; i + 1 < header_length.length; i += 2) { - String k = new String(header_buffer, offset, header_length[i], UTF_8); - offset += header_length[i]; - String v = new String(header_buffer, offset, header_length[i + 1], UTF_8); - offset += header_length[i + 1]; - m_connection.setRequestProperty(k, v); + for (Map.Entry header : headers.entrySet()) { + m_connection.setRequestProperty(header.getKey(), header.getValue()); } } @@ -188,9 +185,25 @@ public class HttpClient { private static final String ANDROID_DEVICE_CLASS_PC = "Android.PC"; private static final String ANDROID_DEVICE_CLASS_PHONE = "Android.Phone"; + static Map convertHeaders( + int[] header_length, + byte[] header_buffer + ) { + int offset = 0; + Map result = new HashMap<>(header_length.length); + for (int i = 0; i + 1 < header_length.length; i += 2) { + String k = new String(header_buffer, offset, header_length[i], UTF_8); + offset += header_length[i]; + String v = new String(header_buffer, offset, header_length[i + 1], UTF_8); + offset += header_length[i + 1]; + result.put(k, v); + } + return result; + } + /** Shim FutureTask: we would like to @Keep the cancel method for JNI */ static class FutureShim extends FutureTask { - FutureShim(Request inner) { + FutureShim(HttpClientRequest inner) { super(inner, true); } @@ -202,7 +215,12 @@ public boolean cancel(boolean mayInterruptIfRunning) { } public HttpClient(Context context) { + this(context, new HttpClientRequest.Factory.AndroidUrlConnection()); + } + + public HttpClient(Context context, HttpClientRequest.Factory requestFactory) { m_context = context; + m_requestFactory = requestFactory; String path = System.getProperty("java.io.tmpdir"); setCacheFilePath(path); setDeviceInfo(calculateID(context), Build.MANUFACTURER, Build.MODEL); @@ -376,7 +394,8 @@ public FutureTask createTask( int[] header_index, byte[] header_buffer) { try { - Request r = new Request(this, url, method, body, request_id, header_index, header_buffer); + Map headers = HttpClient.convertHeaders(header_index, header_buffer); + HttpClientRequest r = m_requestFactory.create(this, url, method, body, request_id, headers); return new FutureShim(r); } catch (Exception e) { return null; @@ -393,4 +412,5 @@ public void executeTask(FutureTask t) { private android.net.ConnectivityManager m_connectivityManager; private PowerInfoReceiver m_power_receiver; private final Context m_context; -} + private final HttpClientRequest.Factory m_requestFactory; +} \ No newline at end of file diff --git a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClientRequest.java b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClientRequest.java new file mode 100644 index 000000000..97d5cd320 --- /dev/null +++ b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClientRequest.java @@ -0,0 +1,31 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +package com.microsoft.applications.events; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.util.Map; + +public interface HttpClientRequest extends Runnable { + + interface Factory { + HttpClientRequest create( + @NonNull HttpClient parent, + String url, + String method, + byte[] body, + String request_id, + @NonNull Map headers + ) throws IOException; + + class AndroidUrlConnection implements HttpClientRequest.Factory { + @Override + public HttpClientRequest create(@NonNull HttpClient parent, String url, String method, byte[] body, String request_id, @NonNull Map headers) throws IOException { + return new Request(parent, url, method, body, request_id, headers); + } + } + } +} \ No newline at end of file From 6fb1cfd06e84181a1e94f36a5ae0c9993e02e6a1 Mon Sep 17 00:00:00 2001 From: Alexander Gavrishev Date: Wed, 1 Feb 2023 09:38:21 +0200 Subject: [PATCH 2/2] Remove Map of headers --- .../events/maesdktest/ExampleUnitTest.java | 21 --------- .../maesdktest/HttpClientRequestTest.java | 40 ++++++++++++++++ .../applications/events/HttpClient.java | 27 +++-------- .../events/HttpClientRequest.java | 47 +++++++++++++++++-- 4 files changed, 90 insertions(+), 45 deletions(-) delete mode 100644 lib/android_build/app/src/test/java/com/microsoft/applications/events/maesdktest/ExampleUnitTest.java create mode 100644 lib/android_build/app/src/test/java/com/microsoft/applications/events/maesdktest/HttpClientRequestTest.java diff --git a/lib/android_build/app/src/test/java/com/microsoft/applications/events/maesdktest/ExampleUnitTest.java b/lib/android_build/app/src/test/java/com/microsoft/applications/events/maesdktest/ExampleUnitTest.java deleted file mode 100644 index b381c38e9..000000000 --- a/lib/android_build/app/src/test/java/com/microsoft/applications/events/maesdktest/ExampleUnitTest.java +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 -// -package com.microsoft.applications.events.maesdktest; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} diff --git a/lib/android_build/app/src/test/java/com/microsoft/applications/events/maesdktest/HttpClientRequestTest.java b/lib/android_build/app/src/test/java/com/microsoft/applications/events/maesdktest/HttpClientRequestTest.java new file mode 100644 index 000000000..136b096a2 --- /dev/null +++ b/lib/android_build/app/src/test/java/com/microsoft/applications/events/maesdktest/HttpClientRequestTest.java @@ -0,0 +1,40 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +package com.microsoft.applications.events.maesdktest; + +import com.microsoft.applications.events.HttpClientRequest; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.*; + +public class HttpClientRequestTest { + @Test + public void testHeaders() { + byte[] bytes = "key1value1key33key2value2".getBytes(UTF_8); + int[] header_lengths = new int[] { 4, 6, 5, 0, 4, 6 }; + + HttpClientRequest.Headers headers = new HttpClientRequest.Headers(header_lengths, bytes); + ArrayList actual = new ArrayList<>(); + while (headers.hasNext()) { + HttpClientRequest.HeaderEntry entry = headers.next(); + actual.add(entry.key); + actual.add(entry.value); + } + + Assert.assertEquals(6, headers.length); + Assert.assertArrayEquals( + new String[] {"key1", "value1", "key33", "", "key2", "value2"}, + actual.toArray() + ); + } +} diff --git a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClient.java b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClient.java index eb815c2f5..79f1402bb 100644 --- a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClient.java +++ b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClient.java @@ -36,7 +36,7 @@ import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; -import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -97,7 +97,7 @@ class Request implements HttpClientRequest { String method, byte[] body, String request_id, - @NonNull Map headers) + @NonNull Headers headers) throws java.io.IOException { m_parent = parent; m_connection = (HttpURLConnection) parent.newUrl(url).openConnection(); @@ -108,8 +108,9 @@ class Request implements HttpClientRequest { m_connection.setDoOutput(true); } m_request_id = request_id; - for (Map.Entry header : headers.entrySet()) { - m_connection.setRequestProperty(header.getKey(), header.getValue()); + while (headers.hasNext()) { + HeaderEntry header = headers.next(); + m_connection.setRequestProperty(header.key, header.value); } } @@ -185,22 +186,6 @@ public class HttpClient { private static final String ANDROID_DEVICE_CLASS_PC = "Android.PC"; private static final String ANDROID_DEVICE_CLASS_PHONE = "Android.Phone"; - static Map convertHeaders( - int[] header_length, - byte[] header_buffer - ) { - int offset = 0; - Map result = new HashMap<>(header_length.length); - for (int i = 0; i + 1 < header_length.length; i += 2) { - String k = new String(header_buffer, offset, header_length[i], UTF_8); - offset += header_length[i]; - String v = new String(header_buffer, offset, header_length[i + 1], UTF_8); - offset += header_length[i + 1]; - result.put(k, v); - } - return result; - } - /** Shim FutureTask: we would like to @Keep the cancel method for JNI */ static class FutureShim extends FutureTask { FutureShim(HttpClientRequest inner) { @@ -394,7 +379,7 @@ public FutureTask createTask( int[] header_index, byte[] header_buffer) { try { - Map headers = HttpClient.convertHeaders(header_index, header_buffer); + HttpClientRequest.Headers headers = new HttpClientRequest.Headers(header_index, header_buffer); HttpClientRequest r = m_requestFactory.create(this, url, method, body, request_id, headers); return new FutureShim(r); } catch (Exception e) { diff --git a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClientRequest.java b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClientRequest.java index 97d5cd320..106ccde82 100644 --- a/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClientRequest.java +++ b/lib/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClientRequest.java @@ -7,7 +7,9 @@ import androidx.annotation.NonNull; import java.io.IOException; -import java.util.Map; +import java.util.Iterator; + +import static java.nio.charset.StandardCharsets.UTF_8; public interface HttpClientRequest extends Runnable { @@ -18,14 +20,53 @@ HttpClientRequest create( String method, byte[] body, String request_id, - @NonNull Map headers + @NonNull Headers headers ) throws IOException; class AndroidUrlConnection implements HttpClientRequest.Factory { @Override - public HttpClientRequest create(@NonNull HttpClient parent, String url, String method, byte[] body, String request_id, @NonNull Map headers) throws IOException { + public HttpClientRequest create(@NonNull HttpClient parent, String url, String method, byte[] body, String request_id, @NonNull Headers headers) throws IOException { return new Request(parent, url, method, body, request_id, headers); } } } + + final class HeaderEntry { + public final String key; + public final String value; + + public HeaderEntry(String key, String value) { + this.key = key; + this.value = value; + } + } + + final class Headers implements Iterator { + public final int length; + private final int[] header_lengths; + private final byte[] buffer; + private int current = 0; + private int offset = 0; + + public Headers(int[] header_lengths, byte[] buffer) { + this.length = header_lengths.length; + this.header_lengths = header_lengths; + this.buffer = buffer; + } + + @Override + public boolean hasNext() { + return current + 2 <= length; + } + + @Override + public HeaderEntry next() { + String k = new String(buffer, offset, header_lengths[current], UTF_8); + offset += header_lengths[current]; + String v = new String(buffer, offset, header_lengths[current + 1], UTF_8); + offset += header_lengths[current + 1]; + current += 2; + return new HeaderEntry(k,v); + } + } } \ No newline at end of file