mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Migrate com.facebook.react.modules.network.RequestBodyUtil to Kotlin (#49584)
Summary: Migrate com.facebook.react.modules.network.RequestBodyUtil to Kotlin ## Changelog: [INTERNAL] - Migrate com.facebook.react.modules.network.RequestBodyUtil to Kotlin Pull Request resolved: https://github.com/facebook/react-native/pull/49584 Test Plan: ```bash yarn test-android yarn android ``` Reviewed By: arushikesarwani94 Differential Revision: D69980421 Pulled By: cortinico fbshipit-source-id: aa66661b2b79afdfd41963e7896b9a01f12af5cb
This commit is contained in:
committed by
Facebook GitHub Bot
parent
f85cca0ee5
commit
0aa8af906d
-166
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Base64;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.RequestBody;
|
||||
import okio.BufferedSink;
|
||||
import okio.ByteString;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
/**
|
||||
* Helper class that provides the necessary methods for creating the RequestBody from a file
|
||||
* specification, such as a contentUri.
|
||||
*/
|
||||
/*package*/ class RequestBodyUtil {
|
||||
|
||||
private static final String CONTENT_ENCODING_GZIP = "gzip";
|
||||
private static final String NAME = "RequestBodyUtil";
|
||||
private static final String TEMP_FILE_SUFFIX = "temp";
|
||||
|
||||
/** Returns whether encode type indicates the body needs to be gzip-ed. */
|
||||
public static boolean isGzipEncoding(@Nullable final String encodingType) {
|
||||
return CONTENT_ENCODING_GZIP.equalsIgnoreCase(encodingType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input stream for a file given by its contentUri. Returns null if the file has not
|
||||
* been found or if an error as occurred.
|
||||
*/
|
||||
public static @Nullable InputStream getFileInputStream(
|
||||
Context context, String fileContentUriStr) {
|
||||
try {
|
||||
Uri fileContentUri = Uri.parse(fileContentUriStr);
|
||||
|
||||
if (fileContentUri.getScheme().startsWith("http")) {
|
||||
return getDownloadFileInputStream(context, fileContentUri);
|
||||
}
|
||||
|
||||
if (fileContentUriStr.startsWith("data:")) {
|
||||
byte[] decodedDataUrString = Base64.decode(fileContentUriStr.split(",")[1], Base64.DEFAULT);
|
||||
return new ByteArrayInputStream(decodedDataUrString);
|
||||
}
|
||||
|
||||
return context.getContentResolver().openInputStream(fileContentUri);
|
||||
} catch (Exception e) {
|
||||
FLog.e(ReactConstants.TAG, "Could not retrieve file for contentUri " + fileContentUriStr, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download and cache a file locally. This should be used when document picker returns a URI that
|
||||
* points to a file on the network. Returns input stream for the downloaded file.
|
||||
*/
|
||||
private static InputStream getDownloadFileInputStream(Context context, Uri uri)
|
||||
throws IOException {
|
||||
final File outputDir = context.getApplicationContext().getCacheDir();
|
||||
final File file = File.createTempFile(NAME, TEMP_FILE_SUFFIX, outputDir);
|
||||
file.deleteOnExit();
|
||||
|
||||
final URL url = new URL(uri.toString());
|
||||
try (FileOutputStream stream = new FileOutputStream(file);
|
||||
InputStream is = url.openStream();
|
||||
ReadableByteChannel channel = Channels.newChannel(is)) {
|
||||
stream.getChannel().transferFrom(channel, 0, Long.MAX_VALUE);
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a RequestBody from a mediaType and gzip-ed body string */
|
||||
public static @Nullable RequestBody createGzip(final MediaType mediaType, final String body) {
|
||||
ByteArrayOutputStream gzipByteArrayOutputStream = new ByteArrayOutputStream();
|
||||
try {
|
||||
OutputStream gzipOutputStream = new GZIPOutputStream(gzipByteArrayOutputStream);
|
||||
gzipOutputStream.write(body.getBytes());
|
||||
gzipOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
return RequestBody.create(mediaType, gzipByteArrayOutputStream.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference:
|
||||
* https://github.com/square/okhttp/blob/8c8c3dbcfa91e28de2e13975ec414e07f153fde4/okhttp/src/commonMain/kotlin/okhttp3/internal/-UtilCommon.kt#L281-L288
|
||||
* Checked exceptions will be ignored
|
||||
*/
|
||||
private static void closeQuietly(Source source) {
|
||||
try {
|
||||
source.close();
|
||||
} catch (RuntimeException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
// noop.
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a RequestBody from a mediaType and inputStream given. */
|
||||
public static RequestBody create(final MediaType mediaType, final InputStream inputStream) {
|
||||
return new RequestBody() {
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() {
|
||||
try {
|
||||
return inputStream.available();
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(BufferedSink sink) throws IOException {
|
||||
Source source = null;
|
||||
try {
|
||||
source = Okio.source(inputStream);
|
||||
sink.writeAll(source);
|
||||
} finally {
|
||||
closeQuietly(source);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Creates a ProgressRequestBody that can be used for showing uploading progress */
|
||||
public static ProgressRequestBody createProgressRequest(
|
||||
RequestBody requestBody, ProgressListener listener) {
|
||||
return new ProgressRequestBody(requestBody, listener);
|
||||
}
|
||||
|
||||
/** Creates a empty RequestBody if required by the http method spec, otherwise use null */
|
||||
public static RequestBody getEmptyBody(String method) {
|
||||
if (method.equals("POST") || method.equals("PUT") || method.equals("PATCH")) {
|
||||
return RequestBody.create(null, ByteString.EMPTY);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION_ERROR") // Conflicting okio versions
|
||||
|
||||
package com.facebook.react.modules.network
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.react.common.ReactConstants
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.net.URL
|
||||
import java.nio.channels.Channels
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.RequestBody
|
||||
import okio.BufferedSink
|
||||
import okio.ByteString
|
||||
import okio.Okio
|
||||
import okio.Source
|
||||
|
||||
/**
|
||||
* Helper class that provides the necessary methods for creating the [RequestBody] from a file
|
||||
* specification, such as a contentUri.
|
||||
*/
|
||||
internal object RequestBodyUtil {
|
||||
private const val CONTENT_ENCODING_GZIP = "gzip"
|
||||
private const val NAME = "RequestBodyUtil"
|
||||
private const val TEMP_FILE_SUFFIX = "temp"
|
||||
|
||||
/** Returns whether encode type indicates the body needs to be gzip-ed. */
|
||||
@JvmStatic
|
||||
fun isGzipEncoding(encodingType: String?): Boolean {
|
||||
return CONTENT_ENCODING_GZIP.equals(encodingType, ignoreCase = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input stream for a file given by its contentUri. Returns null if the file has not
|
||||
* been found or if an error as occurred.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getFileInputStream(context: Context, fileContentUriStr: String): InputStream? {
|
||||
try {
|
||||
val fileContentUri = Uri.parse(fileContentUriStr)
|
||||
|
||||
if (fileContentUri.scheme?.startsWith("http") == true) {
|
||||
return getDownloadFileInputStream(context, fileContentUri)
|
||||
}
|
||||
|
||||
if (fileContentUriStr.startsWith("data:")) {
|
||||
val decodedDataUrString =
|
||||
Base64.decode(
|
||||
fileContentUriStr
|
||||
.split(",".toRegex())
|
||||
.dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()[1],
|
||||
Base64.DEFAULT)
|
||||
return ByteArrayInputStream(decodedDataUrString)
|
||||
}
|
||||
|
||||
return context.contentResolver.openInputStream(fileContentUri)
|
||||
} catch (e: Exception) {
|
||||
FLog.e(ReactConstants.TAG, "Could not retrieve file for contentUri $fileContentUriStr", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download and cache a file locally. This should be used when document picker returns a URI that
|
||||
* points to a file on the network. Returns input stream for the downloaded file.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun getDownloadFileInputStream(context: Context, uri: Uri): InputStream {
|
||||
val outputDir = context.applicationContext.cacheDir
|
||||
val file = File.createTempFile(NAME, TEMP_FILE_SUFFIX, outputDir)
|
||||
file.deleteOnExit()
|
||||
|
||||
val url = URL(uri.toString())
|
||||
FileOutputStream(file).use { stream ->
|
||||
url.openStream().use { `is` ->
|
||||
Channels.newChannel(`is`).use { channel ->
|
||||
stream.channel.transferFrom(channel, 0, Long.MAX_VALUE)
|
||||
return FileInputStream(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a [RequestBody] from a mediaType and gzip-ed body string. */
|
||||
@JvmStatic
|
||||
fun createGzip(mediaType: MediaType?, body: String): RequestBody? {
|
||||
val gzipByteArrayOutputStream = ByteArrayOutputStream()
|
||||
try {
|
||||
val gzipOutputStream: OutputStream = GZIPOutputStream(gzipByteArrayOutputStream)
|
||||
gzipOutputStream.write(body.toByteArray())
|
||||
gzipOutputStream.close()
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
return RequestBody.create(mediaType, gzipByteArrayOutputStream.toByteArray())
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference:
|
||||
* https://github.com/square/okhttp/blob/8c8c3dbcfa91e28de2e13975ec414e07f153fde4/okhttp/src/commonMain/kotlin/okhttp3/internal/-UtilCommon.kt#L281-L288
|
||||
* Checked exceptions will be ignored
|
||||
*/
|
||||
private fun closeQuietly(source: Source) {
|
||||
try {
|
||||
source.close()
|
||||
} catch (e: RuntimeException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a [RequestBody] from a mediaType and inputStream given. */
|
||||
@JvmStatic
|
||||
fun create(mediaType: MediaType?, inputStream: InputStream): RequestBody {
|
||||
return object : RequestBody() {
|
||||
override fun contentType(): MediaType? {
|
||||
return mediaType
|
||||
}
|
||||
|
||||
override fun contentLength(): Long {
|
||||
return try {
|
||||
inputStream.available().toLong()
|
||||
} catch (e: IOException) {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
var source: Source? = null
|
||||
try {
|
||||
source = Okio.source(inputStream)
|
||||
sink.writeAll(source)
|
||||
} finally {
|
||||
source?.let { closeQuietly(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a [ProgressRequestBody] that can be used for showing uploading progress. */
|
||||
@JvmStatic
|
||||
fun createProgressRequest(
|
||||
requestBody: RequestBody?,
|
||||
listener: ProgressListener?
|
||||
): ProgressRequestBody {
|
||||
return ProgressRequestBody(requestBody, listener)
|
||||
}
|
||||
|
||||
/** Creates an empty [RequestBody] if required by the http method spec, otherwise use null. */
|
||||
@JvmStatic
|
||||
fun getEmptyBody(method: String): RequestBody? {
|
||||
return if (method == "POST" || method == "PUT" || method == "PATCH") {
|
||||
@Suppress("DEPRECATION") RequestBody.create(null, ByteString.EMPTY)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// Conflicting okhttp versions
|
||||
@file:Suppress("DEPRECATION_ERROR")
|
||||
|
||||
package com.facebook.react.modules.network
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.util.zip.GZIPInputStream
|
||||
import okhttp3.MediaType
|
||||
import okio.Buffer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class RequestBodyUtilTest {
|
||||
|
||||
@Test
|
||||
fun testIsGzipEncoding() {
|
||||
assertThat(RequestBodyUtil.isGzipEncoding("gzip")).isTrue()
|
||||
assertThat(RequestBodyUtil.isGzipEncoding("GzIp")).isTrue()
|
||||
assertThat(RequestBodyUtil.isGzipEncoding("identity")).isFalse()
|
||||
assertThat(RequestBodyUtil.isGzipEncoding(null)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetFileInputStreamWithHttpUri() {
|
||||
val context = mock<Context>()
|
||||
val fileUri = "http://example.com/file"
|
||||
|
||||
// Since getDownloadFileInputStream is private and not mocked, it will throw an exception.
|
||||
val result = RequestBodyUtil.getFileInputStream(context, fileUri)
|
||||
|
||||
assertThat(result).isNull() // Expected null due to exception handling
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetFileInputStreamWithDataUri() {
|
||||
val context = mock<Context>()
|
||||
val fileUri = "data:text/plain;base64,SGVsbG8gV29ybGQ="
|
||||
|
||||
val result = RequestBodyUtil.getFileInputStream(context, fileUri)
|
||||
|
||||
assertThat("Hello World").isEqualTo(result?.bufferedReader()?.use { it.readText() })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetFileInputStreamWithContentUri() {
|
||||
val context = mock<Context>()
|
||||
val contentResolver = mock<ContentResolver>()
|
||||
whenever(context.contentResolver).thenReturn(contentResolver)
|
||||
|
||||
val fileUri = "content://com.example.provider/file"
|
||||
val testInputStream = ByteArrayInputStream("Sample Content".toByteArray())
|
||||
|
||||
whenever(contentResolver.openInputStream(Uri.parse(fileUri))).thenReturn(testInputStream)
|
||||
|
||||
val result = RequestBodyUtil.getFileInputStream(context, fileUri)
|
||||
|
||||
assertThat("Sample Content").isEqualTo(result?.bufferedReader()?.use { it.readText() })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetFileInputStreamWithInvalidUri() {
|
||||
val context = mock<Context>()
|
||||
val invalidUri = "invalid-uri"
|
||||
|
||||
val result = RequestBodyUtil.getFileInputStream(context, invalidUri)
|
||||
|
||||
assertThat(result).isNull() // Expected null due to exception handling
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateGzipWithValidInput() {
|
||||
val mediaType = checkNotNull(MediaType.parse("text/plain"))
|
||||
val input = "Hello Gzip"
|
||||
|
||||
val requestBody = RequestBodyUtil.createGzip(mediaType, input)
|
||||
|
||||
checkNotNull(requestBody)
|
||||
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
|
||||
val gzipInputStream = GZIPInputStream(ByteArrayInputStream(buffer.readByteArray()))
|
||||
val result = gzipInputStream.bufferedReader().use { it.readText() }
|
||||
|
||||
assertThat(input).isEqualTo(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateGzipWithEmptyInput() {
|
||||
val mediaType = checkNotNull(MediaType.parse("text/plain"))
|
||||
val input = ""
|
||||
|
||||
val requestBody = RequestBodyUtil.createGzip(mediaType, input)
|
||||
|
||||
checkNotNull(requestBody)
|
||||
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
|
||||
val gzipInputStream = GZIPInputStream(ByteArrayInputStream(buffer.readByteArray()))
|
||||
val result = gzipInputStream.bufferedReader().use { it.readText() }
|
||||
|
||||
assertThat(input).isEqualTo(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateGzipWithNullInput() {
|
||||
val mediaType = checkNotNull(MediaType.parse("text/plain"))
|
||||
|
||||
val requestBody = RequestBodyUtil.createGzip(mediaType, "")
|
||||
|
||||
checkNotNull(requestBody)
|
||||
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
|
||||
val gzipInputStream = GZIPInputStream(ByteArrayInputStream(buffer.readByteArray()))
|
||||
val result = gzipInputStream.bufferedReader().use { it.readText() }
|
||||
|
||||
assertThat(result).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateWithInputStream() {
|
||||
val mediaType = checkNotNull(MediaType.parse("text/plain"))
|
||||
val inputStream = ByteArrayInputStream("Test InputStream".toByteArray())
|
||||
|
||||
val requestBody = RequestBodyUtil.create(mediaType, inputStream)
|
||||
|
||||
checkNotNull(requestBody)
|
||||
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
|
||||
assertThat("Test InputStream").isEqualTo(buffer.readUtf8())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user