mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-03 22:48:25 +08:00
Implement Blob support for XMLHttpRequest
Summary: This PR is a followup to https://github.com/facebook/react-native/pull/11417 and should be merged after that one is merged. 1. Add support for creating blobs from strings, not just other blobs 1. Add the `File` constructor which is a superset of `Blob` 1. Add the `FileReader` API which can be used to read blobs as strings or data url (base64) 1. Add support for uploading and downloading blobs via `XMLHttpRequest` and `fetch` 1. Add ability to download local files on Android so you can do `fetch(uri).then(res => res.blob())` to get a blob for a local file (iOS already supported this) 1. Clone the repo https://github.com/expo/react-native-blob-test 1. Change the `package.json` and update `react-native` dependency to point to this branch, then run `npm install` 1. Run the `server.js` file with `node server.js` 1. Open the `index.common.js` file and replace `localhost` with your computer's IP address 1. Start the packager with `yarn start` and run the app on your device If everything went well, all tests should pass, and you should see a screen like this: ! Pull to rerun all tests or tap on specific test to re-run it [GENERAL] [FEATURE] [Blob] - Implement blob support for XMLHttpRequest Closes https://github.com/facebook/react-native/pull/11573 Reviewed By: shergin Differential Revision: D6082054 Pulled By: hramos fbshipit-source-id: cc9c174fdefdfaf6e5d9fd7b300120a01a50e8c1
This commit is contained in:
committed by
Facebook Github Bot
parent
3fc33bb54f
commit
be56a3efee
@@ -14,11 +14,13 @@ rn_android_library(
|
||||
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"),
|
||||
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
|
||||
react_native_dep("third-party/java/jsr-305:jsr-305"),
|
||||
react_native_dep("third-party/java/okhttp:okhttp3"),
|
||||
react_native_dep("third-party/java/okio:okio"),
|
||||
react_native_target("java/com/facebook/react:react"),
|
||||
react_native_target("java/com/facebook/react/bridge:bridge"),
|
||||
react_native_target("java/com/facebook/react/common:common"),
|
||||
react_native_target("java/com/facebook/react/module/annotations:annotations"),
|
||||
react_native_target("java/com/facebook/react/modules/network:network"),
|
||||
react_native_target("java/com/facebook/react/modules/websocket:websocket"),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -7,9 +7,15 @@
|
||||
*/
|
||||
package com.facebook.react.modules.blob;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
@@ -19,13 +25,25 @@ import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.modules.network.NetworkingModule;
|
||||
import com.facebook.react.modules.websocket.WebSocketModule;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import okio.ByteString;
|
||||
|
||||
@ReactModule(name = BlobModule.NAME)
|
||||
@@ -35,27 +53,100 @@ public class BlobModule extends ReactContextBaseJavaModule {
|
||||
|
||||
private final Map<String, byte[]> mBlobs = new HashMap<>();
|
||||
|
||||
protected final WebSocketModule.ContentHandler mContentHandler =
|
||||
new WebSocketModule.ContentHandler() {
|
||||
@Override
|
||||
public void onMessage(String text, WritableMap params) {
|
||||
params.putString("data", text);
|
||||
private final WebSocketModule.ContentHandler mWebSocketContentHandler =
|
||||
new WebSocketModule.ContentHandler() {
|
||||
@Override
|
||||
public void onMessage(String text, WritableMap params) {
|
||||
params.putString("data", text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(ByteString bytes, WritableMap params) {
|
||||
byte[] data = bytes.toByteArray();
|
||||
|
||||
WritableMap blob = Arguments.createMap();
|
||||
|
||||
blob.putString("blobId", store(data));
|
||||
blob.putInt("offset", 0);
|
||||
blob.putInt("size", data.length);
|
||||
|
||||
params.putMap("data", blob);
|
||||
params.putString("type", "blob");
|
||||
}
|
||||
};
|
||||
|
||||
private final NetworkingModule.UriHandler mNetworkingUriHandler =
|
||||
new NetworkingModule.UriHandler() {
|
||||
@Override
|
||||
public boolean supports(Uri uri, String responseType) {
|
||||
String scheme = uri.getScheme();
|
||||
boolean isRemote = scheme.equals("http") || scheme.equals("https");
|
||||
|
||||
return (!isRemote && responseType.equals("blob"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public WritableMap fetch(Uri uri) throws IOException {
|
||||
byte[] data = getBytesFromUri(uri);
|
||||
|
||||
WritableMap blob = Arguments.createMap();
|
||||
blob.putString("blobId", store(data));
|
||||
blob.putInt("offset", 0);
|
||||
blob.putInt("size", data.length);
|
||||
blob.putString("type", getMimeTypeFromUri(uri));
|
||||
|
||||
// Needed for files
|
||||
blob.putString("name", getNameFromUri(uri));
|
||||
blob.putDouble("lastModified", getLastModifiedFromUri(uri));
|
||||
|
||||
return blob;
|
||||
}
|
||||
};
|
||||
|
||||
private final NetworkingModule.RequestBodyHandler mNetworkingRequestBodyHandler =
|
||||
new NetworkingModule.RequestBodyHandler() {
|
||||
@Override
|
||||
public boolean supports(ReadableMap data) {
|
||||
return data.hasKey("blob");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestBody toRequestBody(ReadableMap data, String contentType) {
|
||||
String type = contentType;
|
||||
if (data.hasKey("type") && !data.getString("type").isEmpty()) {
|
||||
type = data.getString("type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(ByteString bytes, WritableMap params) {
|
||||
byte[] data = bytes.toByteArray();
|
||||
|
||||
WritableMap blob = Arguments.createMap();
|
||||
|
||||
blob.putString("blobId", store(data));
|
||||
blob.putInt("offset", 0);
|
||||
blob.putInt("size", data.length);
|
||||
|
||||
params.putMap("data", blob);
|
||||
params.putString("type", "blob");
|
||||
if (type == null) {
|
||||
type = "application/octet-stream";
|
||||
}
|
||||
};
|
||||
ReadableMap blob = data.getMap("blob");
|
||||
String blobId = blob.getString("blobId");
|
||||
byte[] bytes = resolve(
|
||||
blobId,
|
||||
blob.getInt("offset"),
|
||||
blob.getInt("size"));
|
||||
|
||||
return RequestBody.create(MediaType.parse(type), bytes);
|
||||
}
|
||||
};
|
||||
|
||||
private final NetworkingModule.ResponseHandler mNetworkingResponseHandler =
|
||||
new NetworkingModule.ResponseHandler() {
|
||||
@Override
|
||||
public boolean supports(String responseType) {
|
||||
return responseType.equals("blob");
|
||||
}
|
||||
|
||||
@Override
|
||||
public WritableMap toResponseData(ResponseBody body) throws IOException {
|
||||
byte[] data = body.bytes();
|
||||
WritableMap blob = Arguments.createMap();
|
||||
blob.putString("blobId", store(data));
|
||||
blob.putInt("offset", 0);
|
||||
blob.putInt("size", data.length);
|
||||
return blob;
|
||||
}
|
||||
};
|
||||
|
||||
public BlobModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
@@ -67,8 +158,7 @@ public class BlobModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Map getConstants() {
|
||||
public @Nullable Map<String, Object> getConstants() {
|
||||
// The application can register BlobProvider as a ContentProvider so that blobs are resolvable.
|
||||
// If it does, it needs to tell us what authority was used via this string resource.
|
||||
Resources resources = getReactApplicationContext().getResources();
|
||||
@@ -78,8 +168,8 @@ public class BlobModule extends ReactContextBaseJavaModule {
|
||||
return null;
|
||||
}
|
||||
|
||||
return MapBuilder.of(
|
||||
"BLOB_URI_SCHEME", "content", "BLOB_URI_HOST", resources.getString(resourceId));
|
||||
return MapBuilder.<String, Object>of(
|
||||
"BLOB_URI_SCHEME", "content", "BLOB_URI_HOST", resources.getString(resourceId));
|
||||
}
|
||||
|
||||
public String store(byte[] data) {
|
||||
@@ -96,8 +186,7 @@ public class BlobModule extends ReactContextBaseJavaModule {
|
||||
mBlobs.remove(blobId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public byte[] resolve(Uri uri) {
|
||||
public @Nullable byte[] resolve(Uri uri) {
|
||||
String blobId = uri.getLastPathSegment();
|
||||
int offset = 0;
|
||||
int size = -1;
|
||||
@@ -112,8 +201,7 @@ public class BlobModule extends ReactContextBaseJavaModule {
|
||||
return resolve(blobId, offset, size);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public byte[] resolve(String blobId, int offset, int size) {
|
||||
public @Nullable byte[] resolve(String blobId, int offset, int size) {
|
||||
byte[] data = mBlobs.get(blobId);
|
||||
if (data == null) {
|
||||
return null;
|
||||
@@ -121,33 +209,101 @@ public class BlobModule extends ReactContextBaseJavaModule {
|
||||
if (size == -1) {
|
||||
size = data.length - offset;
|
||||
}
|
||||
if (offset > 0) {
|
||||
if (offset > 0 || size != data.length) {
|
||||
data = Arrays.copyOfRange(data, offset, offset + size);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public byte[] resolve(ReadableMap blob) {
|
||||
public @Nullable byte[] resolve(ReadableMap blob) {
|
||||
return resolve(blob.getString("blobId"), blob.getInt("offset"), blob.getInt("size"));
|
||||
}
|
||||
|
||||
private byte[] getBytesFromUri(Uri contentUri) throws IOException {
|
||||
InputStream is = getReactApplicationContext().getContentResolver().openInputStream(contentUri);
|
||||
|
||||
if (is == null) {
|
||||
throw new FileNotFoundException("File not found for " + contentUri);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
|
||||
int bufferSize = 1024;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
int len;
|
||||
while ((len = is.read(buffer)) != -1) {
|
||||
byteBuffer.write(buffer, 0, len);
|
||||
}
|
||||
return byteBuffer.toByteArray();
|
||||
}
|
||||
|
||||
private String getNameFromUri(Uri contentUri) {
|
||||
if (contentUri.getScheme().equals("file")) {
|
||||
return contentUri.getLastPathSegment();
|
||||
}
|
||||
String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
|
||||
Cursor metaCursor = getReactApplicationContext()
|
||||
.getContentResolver()
|
||||
.query(contentUri, projection, null, null, null);
|
||||
if (metaCursor != null) {
|
||||
try {
|
||||
if (metaCursor.moveToFirst()) {
|
||||
return metaCursor.getString(0);
|
||||
}
|
||||
} finally {
|
||||
metaCursor.close();
|
||||
}
|
||||
}
|
||||
return contentUri.getLastPathSegment();
|
||||
}
|
||||
|
||||
private long getLastModifiedFromUri(Uri contentUri) {
|
||||
if (contentUri.getScheme().equals("file")) {
|
||||
return new File(contentUri.toString()).lastModified();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private String getMimeTypeFromUri(Uri contentUri) {
|
||||
String type = getReactApplicationContext().getContentResolver().getType(contentUri);
|
||||
|
||||
if (type == null) {
|
||||
String ext = MimeTypeMap.getFileExtensionFromUrl(contentUri.getPath());
|
||||
if (ext != null) {
|
||||
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
|
||||
}
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
type = "";
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private WebSocketModule getWebSocketModule() {
|
||||
return getReactApplicationContext().getNativeModule(WebSocketModule.class);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void enableBlobSupport(final int id) {
|
||||
getWebSocketModule().setContentHandler(id, mContentHandler);
|
||||
public void addNetworkingHandler() {
|
||||
NetworkingModule networkingModule = getReactApplicationContext().getNativeModule(NetworkingModule.class);
|
||||
networkingModule.addUriHandler(mNetworkingUriHandler);
|
||||
networkingModule.addRequestBodyHandler(mNetworkingRequestBodyHandler);
|
||||
networkingModule.addResponseHandler(mNetworkingResponseHandler);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void disableBlobSupport(final int id) {
|
||||
public void addWebSocketHandler(final int id) {
|
||||
getWebSocketModule().setContentHandler(id, mWebSocketContentHandler);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void removeWebSocketHandler(final int id) {
|
||||
getWebSocketModule().setContentHandler(id, null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void sendBlob(ReadableMap blob, int id) {
|
||||
public void sendOverSocket(ReadableMap blob, int id) {
|
||||
byte[] data = resolve(blob.getString("blobId"), blob.getInt("offset"), blob.getInt("size"));
|
||||
|
||||
if (data != null) {
|
||||
@@ -160,15 +316,27 @@ public class BlobModule extends ReactContextBaseJavaModule {
|
||||
@ReactMethod
|
||||
public void createFromParts(ReadableArray parts, String blobId) {
|
||||
int totalBlobSize = 0;
|
||||
ArrayList<ReadableMap> partList = new ArrayList<>(parts.size());
|
||||
ArrayList<byte[]> partList = new ArrayList<>(parts.size());
|
||||
for (int i = 0; i < parts.size(); i++) {
|
||||
ReadableMap part = parts.getMap(i);
|
||||
totalBlobSize += part.getInt("size");
|
||||
partList.add(i, part);
|
||||
switch (part.getString("type")) {
|
||||
case "blob":
|
||||
ReadableMap blob = part.getMap("data");
|
||||
totalBlobSize += blob.getInt("size");
|
||||
partList.add(i, resolve(blob));
|
||||
break;
|
||||
case "string":
|
||||
byte[] bytes = part.getString("data").getBytes(Charset.forName("UTF-8"));
|
||||
totalBlobSize += bytes.length;
|
||||
partList.add(i, bytes);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid type for blob: " + part.getString("type"));
|
||||
}
|
||||
}
|
||||
ByteBuffer buffer = ByteBuffer.allocate(totalBlobSize);
|
||||
for (ReadableMap part : partList) {
|
||||
buffer.put(resolve(part));
|
||||
for (byte[] bytes : partList) {
|
||||
buffer.put(bytes);
|
||||
}
|
||||
store(buffer.array(), blobId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.blob;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
|
||||
@ReactModule(name = FileReaderModule.NAME)
|
||||
public class FileReaderModule extends ReactContextBaseJavaModule {
|
||||
|
||||
protected static final String NAME = "FileReaderModule";
|
||||
private static final String ERROR_INVALID_BLOB = "ERROR_INVALID_BLOB";
|
||||
|
||||
public FileReaderModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
private BlobModule getBlobModule() {
|
||||
return getReactApplicationContext().getNativeModule(BlobModule.class);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void readAsText(ReadableMap blob, String encoding, Promise promise) {
|
||||
|
||||
byte[] bytes = getBlobModule().resolve(
|
||||
blob.getString("blobId"),
|
||||
blob.getInt("offset"),
|
||||
blob.getInt("size"));
|
||||
|
||||
if (bytes == null) {
|
||||
promise.reject(ERROR_INVALID_BLOB, "The specified blob is invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
promise.resolve(new String(bytes, encoding));
|
||||
} catch (Exception e) {
|
||||
promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void readAsDataURL(ReadableMap blob, Promise promise) {
|
||||
byte[] bytes = getBlobModule().resolve(
|
||||
blob.getString("blobId"),
|
||||
blob.getInt("offset"),
|
||||
blob.getInt("size"));
|
||||
|
||||
if (bytes == null) {
|
||||
promise.reject(ERROR_INVALID_BLOB, "The specified blob is invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("data:");
|
||||
|
||||
if (blob.hasKey("type") && !blob.getString("type").isEmpty()) {
|
||||
sb.append(blob.getString("type"));
|
||||
} else {
|
||||
sb.append("application/octet-stream");
|
||||
}
|
||||
|
||||
sb.append(";base64,");
|
||||
sb.append(Base64.encodeToString(bytes, Base64.NO_WRAP));
|
||||
|
||||
promise.resolve(sb.toString());
|
||||
} catch (Exception e) {
|
||||
promise.reject(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,20 +6,9 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
@@ -35,6 +24,17 @@ import com.facebook.react.common.network.OkHttpCallUtil;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.CookieJar;
|
||||
@@ -56,6 +56,52 @@ import okio.ByteString;
|
||||
@ReactModule(name = NetworkingModule.NAME)
|
||||
public final class NetworkingModule extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* Allows to implement a custom fetching process for specific URIs. It is the handler's job
|
||||
* to fetch the URI and return the JS body payload.
|
||||
*/
|
||||
public interface UriHandler {
|
||||
/**
|
||||
* Returns if the handler should be used for an URI.
|
||||
*/
|
||||
boolean supports(Uri uri, String responseType);
|
||||
|
||||
/**
|
||||
* Fetch the URI and return the JS body payload.
|
||||
*/
|
||||
WritableMap fetch(Uri uri) throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows adding custom handling to build the {@link RequestBody} from the JS body payload.
|
||||
*/
|
||||
public interface RequestBodyHandler {
|
||||
/**
|
||||
* Returns if the handler should be used for a JS body payload.
|
||||
*/
|
||||
boolean supports(ReadableMap map);
|
||||
|
||||
/**
|
||||
* Returns the {@link RequestBody} for the JS body payload.
|
||||
*/
|
||||
RequestBody toRequestBody(ReadableMap map, String contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows adding custom handling to build the JS body payload from the {@link ResponseBody}.
|
||||
*/
|
||||
public interface ResponseHandler {
|
||||
/**
|
||||
* Returns if the handler should be used for a response type.
|
||||
*/
|
||||
boolean supports(String responseType);
|
||||
|
||||
/**
|
||||
* Returns the JS body payload for the {@link ResponseBody}.
|
||||
*/
|
||||
WritableMap toResponseData(ResponseBody body) throws IOException;
|
||||
}
|
||||
|
||||
protected static final String NAME = "Networking";
|
||||
|
||||
private static final String CONTENT_ENCODING_HEADER_NAME = "content-encoding";
|
||||
@@ -73,6 +119,9 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
|
||||
private final @Nullable String mDefaultUserAgent;
|
||||
private final CookieJarContainer mCookieJarContainer;
|
||||
private final Set<Integer> mRequestIds;
|
||||
private final List<RequestBodyHandler> mRequestBodyHandlers = new ArrayList<>();
|
||||
private final List<UriHandler> mUriHandlers = new ArrayList<>();
|
||||
private final List<ResponseHandler> mResponseHandlers = new ArrayList<>();
|
||||
private boolean mShuttingDown;
|
||||
|
||||
/* package */ NetworkingModule(
|
||||
@@ -154,6 +203,34 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
|
||||
|
||||
mCookieHandler.destroy();
|
||||
mCookieJarContainer.removeCookieJar();
|
||||
|
||||
mRequestBodyHandlers.clear();
|
||||
mResponseHandlers.clear();
|
||||
mUriHandlers.clear();
|
||||
}
|
||||
|
||||
public void addUriHandler(UriHandler handler) {
|
||||
mUriHandlers.add(handler);
|
||||
}
|
||||
|
||||
public void addRequestBodyHandler(RequestBodyHandler handler) {
|
||||
mRequestBodyHandlers.add(handler);
|
||||
}
|
||||
|
||||
public void addResponseHandler(ResponseHandler handler) {
|
||||
mResponseHandlers.add(handler);
|
||||
}
|
||||
|
||||
public void removeUriHandler(UriHandler handler) {
|
||||
mUriHandlers.remove(handler);
|
||||
}
|
||||
|
||||
public void removeRequestBodyHandler(RequestBodyHandler handler) {
|
||||
mRequestBodyHandlers.remove(handler);
|
||||
}
|
||||
|
||||
public void removeResponseHandler(ResponseHandler handler) {
|
||||
mResponseHandlers.remove(handler);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
@@ -170,13 +247,31 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
|
||||
final boolean useIncrementalUpdates,
|
||||
int timeout,
|
||||
boolean withCredentials) {
|
||||
final RCTDeviceEventEmitter eventEmitter = getEventEmitter();
|
||||
|
||||
try {
|
||||
Uri uri = Uri.parse(url);
|
||||
|
||||
// Check if a handler is registered
|
||||
for (UriHandler handler : mUriHandlers) {
|
||||
if (handler.supports(uri, responseType)) {
|
||||
WritableMap res = handler.fetch(uri);
|
||||
ResponseUtil.onDataReceived(eventEmitter, requestId, res);
|
||||
ResponseUtil.onRequestSuccess(eventEmitter, requestId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
ResponseUtil.onRequestError(eventEmitter, requestId, e.getMessage(), e);
|
||||
return;
|
||||
}
|
||||
|
||||
Request.Builder requestBuilder = new Request.Builder().url(url);
|
||||
|
||||
if (requestId != 0) {
|
||||
requestBuilder.tag(requestId);
|
||||
}
|
||||
|
||||
final RCTDeviceEventEmitter eventEmitter = getEventEmitter();
|
||||
OkHttpClient.Builder clientBuilder = mClient.newBuilder();
|
||||
|
||||
if (!withCredentials) {
|
||||
@@ -237,8 +332,22 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
|
||||
String contentEncoding = requestHeaders.get(CONTENT_ENCODING_HEADER_NAME);
|
||||
requestBuilder.headers(requestHeaders);
|
||||
|
||||
// Check if a handler is registered
|
||||
RequestBodyHandler handler = null;
|
||||
if (data != null) {
|
||||
for (RequestBodyHandler curHandler : mRequestBodyHandlers) {
|
||||
if (curHandler.supports(data)) {
|
||||
handler = curHandler;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method));
|
||||
} else if (handler != null) {
|
||||
RequestBody requestBody = handler.toRequestBody(data, contentType);
|
||||
requestBuilder.method(method, requestBody);
|
||||
} else if (data.hasKey(REQUEST_BODY_KEY_STRING)) {
|
||||
if (contentType == null) {
|
||||
ResponseUtil.onRequestError(
|
||||
@@ -360,6 +469,16 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
|
||||
|
||||
ResponseBody responseBody = response.body();
|
||||
try {
|
||||
// Check if a handler is registered
|
||||
for (ResponseHandler handler : mResponseHandlers) {
|
||||
if (handler.supports(responseType)) {
|
||||
WritableMap res = handler.toResponseData(responseBody);
|
||||
ResponseUtil.onDataReceived(eventEmitter, requestId, res);
|
||||
ResponseUtil.onRequestSuccess(eventEmitter, requestId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If JS wants progress updates during the download, and it requested a text response,
|
||||
// periodically send response data updates to JS.
|
||||
if (useIncrementalUpdates && responseType.equals("text")) {
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
|
||||
package com.facebook.react.modules.network;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
/**
|
||||
* Util methods to send network responses to JS.
|
||||
*/
|
||||
@@ -72,6 +72,17 @@ public class ResponseUtil {
|
||||
eventEmitter.emit("didReceiveNetworkData", args);
|
||||
}
|
||||
|
||||
public static void onDataReceived(
|
||||
RCTDeviceEventEmitter eventEmitter,
|
||||
int requestId,
|
||||
WritableMap data) {
|
||||
WritableArray args = Arguments.createArray();
|
||||
args.pushInt(requestId);
|
||||
args.pushMap(data);
|
||||
|
||||
eventEmitter.emit("didReceiveNetworkData", args);
|
||||
}
|
||||
|
||||
public static void onRequestError(
|
||||
RCTDeviceEventEmitter eventEmitter,
|
||||
int requestId,
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.facebook.react.module.model.ReactModuleInfoProvider;
|
||||
import com.facebook.react.modules.accessibilityinfo.AccessibilityInfoModule;
|
||||
import com.facebook.react.modules.appstate.AppStateModule;
|
||||
import com.facebook.react.modules.blob.BlobModule;
|
||||
import com.facebook.react.modules.blob.FileReaderModule;
|
||||
import com.facebook.react.modules.camera.CameraRollManager;
|
||||
import com.facebook.react.modules.camera.ImageEditingManager;
|
||||
import com.facebook.react.modules.camera.ImageStoreManager;
|
||||
@@ -125,6 +126,14 @@ public class MainReactPackage extends LazyReactPackage {
|
||||
return new BlobModule(context);
|
||||
}
|
||||
}),
|
||||
ModuleSpec.nativeModuleSpec(
|
||||
FileReaderModule.class,
|
||||
new Provider<NativeModule>() {
|
||||
@Override
|
||||
public NativeModule get() {
|
||||
return new FileReaderModule(context);
|
||||
}
|
||||
}),
|
||||
ModuleSpec.nativeModuleSpec(
|
||||
AsyncStorageModule.class,
|
||||
new Provider<NativeModule>() {
|
||||
|
||||
Reference in New Issue
Block a user