Browse Source

Add Android fixed length request implementation #94

Ben Hsieh 8 years ago
parent
commit
deb1b58ce6

+ 100
- 73
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java View File

@@ -13,8 +13,12 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
13 13
 import java.io.ByteArrayInputStream;
14 14
 import java.io.File;
15 15
 import java.io.FileInputStream;
16
+import java.io.FileNotFoundException;
17
+import java.io.FileOutputStream;
16 18
 import java.io.IOException;
17 19
 import java.io.InputStream;
20
+import java.nio.ByteBuffer;
21
+import java.nio.MappedByteBuffer;
18 22
 import java.util.ArrayList;
19 23
 import java.util.HashMap;
20 24
 
@@ -33,19 +37,27 @@ import okio.Sink;
33 37
 public class RNFetchBlobBody extends RequestBody{
34 38
 
35 39
     InputStream requestStream;
36
-    long contentLength;
40
+    long contentLength = 0;
37 41
     long bytesWritten = 0;
38 42
     ReadableArray form;
39 43
     String mTaskId;
40 44
     String rawBody;
41 45
     RNFetchBlobReq.RequestType requestType;
42 46
     MediaType mime;
47
+    File bodyCache;
48
+
43 49
 
44 50
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, ReadableArray form, MediaType contentType) {
45 51
         this.mTaskId = taskId;
46 52
         this.form = form;
47 53
         requestType = type;
48 54
         mime = contentType;
55
+        try {
56
+            bodyCache = createMultipartBodyCache();
57
+            contentLength = bodyCache.length();
58
+        } catch (IOException e) {
59
+            e.printStackTrace();
60
+        }
49 61
     }
50 62
 
51 63
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, String rawBody, MediaType contentType) {
@@ -55,6 +67,10 @@ public class RNFetchBlobBody extends RequestBody{
55 67
         mime = contentType;
56 68
     }
57 69
 
70
+    @Override
71
+    public long contentLength() {
72
+        return contentLength;
73
+    }
58 74
     @Override
59 75
     public MediaType contentType() {
60 76
         return mime;
@@ -67,10 +83,11 @@ public class RNFetchBlobBody extends RequestBody{
67 83
         BufferedSink buffer = Okio.buffer(source);
68 84
         switch (requestType) {
69 85
             case Form:
70
-                writeFormData(sink);
86
+                pipeStreamToSink(new FileInputStream(bodyCache), sink);
71 87
                 break;
72 88
             case SingleFile:
73
-                writeOctetData(sink);
89
+                if(requestStream != null)
90
+                    pipeStreamToSink(requestStream, sink);
74 91
                 break;
75 92
             case AsIs:
76 93
 				writeRawData(sink);
@@ -79,10 +96,58 @@ public class RNFetchBlobBody extends RequestBody{
79 96
         buffer.flush();
80 97
     }
81 98
 
82
-	private void writeFormData(BufferedSink sink) throws IOException {
99
+    private void caculateOctetContentLength() {
100
+        long total = 0;
101
+        // upload from storage
102
+        if (rawBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
103
+            String orgPath = rawBody.substring(RNFetchBlobConst.FILE_PREFIX.length());
104
+            orgPath = RNFetchBlobFS.normalizePath(orgPath);
105
+            // upload file from assets
106
+            if (RNFetchBlobFS.isAsset(orgPath)) {
107
+                try {
108
+                    String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
109
+                    contentLength = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
110
+                    requestStream = RNFetchBlob.RCTContext.getAssets().open(assetName);
111
+                } catch (IOException e) {
112
+//                        e.printStackTrace();
113
+                }
114
+            } else {
115
+                File f = new File(RNFetchBlobFS.normalizePath(orgPath));
116
+                try {
117
+                    if(!f.exists())
118
+                        f.createNewFile();
119
+                    contentLength = f.length();
120
+                    requestStream = new FileInputStream(f);
121
+                } catch (Exception e) {
122
+//                        callback.invoke(e.getLocalizedMessage(), null);
123
+                }
124
+            }
125
+        } else {
126
+            try {
127
+                byte[] bytes = Base64.decode(rawBody, 0);
128
+                contentLength = bytes.length;
129
+                requestStream = new ByteArrayInputStream(bytes);
130
+            } catch(Exception ex) {
131
+                Log.e("error", ex.getLocalizedMessage());
132
+            }
133
+        }
134
+    }
135
+
136
+    /**
137
+     * Create a temp file that contains content of multipart form data content
138
+     * @return The cache file object
139
+     * @throws IOException
140
+     */
141
+    private File createMultipartBodyCache() throws IOException {
83 142
         String boundary = "RNFetchBlob-" + mTaskId;
143
+
144
+        File outputDir = RNFetchBlob.RCTContext.getCacheDir(); // context being the Activity pointer
145
+        File outputFile = File.createTempFile("rnfb-form-tmp", "", outputDir);
146
+        FileOutputStream os = new FileOutputStream(outputFile);
147
+
84 148
         ArrayList<FormField> fields = countFormDataLength();
85 149
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
150
+
86 151
         for(int i = 0;i < fields.size(); i++) {
87 152
             FormField field = fields.get(i);
88 153
             String data = field.data;
@@ -95,7 +160,7 @@ public class RNFetchBlobBody extends RequestBody{
95 160
             if (field.filename != null) {
96 161
                 header += "Content-Disposition: form-data; name=" + name + "; filename=" + field.filename + "\r\n";
97 162
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
98
-                sink.write(header.getBytes());
163
+                os.write(header.getBytes());
99 164
                 // file field header end
100 165
                 // upload from storage
101 166
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
@@ -106,7 +171,7 @@ public class RNFetchBlobBody extends RequestBody{
106 171
                         try {
107 172
                             String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
108 173
                             InputStream in = ctx.getAssets().open(assetName);
109
-                            pipeStreamToSink(in, sink);
174
+                            pipeStreamToFileStream(in, os);
110 175
                         } catch (IOException e) {
111 176
                             Log.e("RNFetchBlob", "Failed to create form data asset :" + orgPath + ", " + e.getLocalizedMessage() );
112 177
                         }
@@ -116,7 +181,7 @@ public class RNFetchBlobBody extends RequestBody{
116 181
                         File file = new File(RNFetchBlobFS.normalizePath(orgPath));
117 182
                         if(file.exists()) {
118 183
                             FileInputStream fs = new FileInputStream(file);
119
-                            pipeStreamToSink(fs, sink);
184
+                            pipeStreamToFileStream(fs, os);
120 185
                         }
121 186
                         else {
122 187
                             Log.e("RNFetchBlob", "Failed to create form data from path :" + orgPath + "file not exists.");
@@ -126,7 +191,7 @@ public class RNFetchBlobBody extends RequestBody{
126 191
                 // base64 embedded file content
127 192
                 else {
128 193
                     byte[] b = Base64.decode(data, 0);
129
-                    sink.write(b);
194
+                    os.write(b);
130 195
                     bytesWritten += b.length;
131 196
                     emitUploadProgress();
132 197
                 }
@@ -136,70 +201,30 @@ public class RNFetchBlobBody extends RequestBody{
136 201
             else {
137 202
                 header += "Content-Disposition: form-data; name=" + name + "\r\n";
138 203
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
139
-                sink.write(header.getBytes());
204
+                os.write(header.getBytes());
140 205
                 byte[] fieldData = field.data.getBytes();
141 206
                 bytesWritten += fieldData.length;
142
-                sink.write(fieldData);
207
+                os.write(fieldData);
143 208
             }
144 209
             // form end
145
-            sink.write("\r\n".getBytes());
210
+            os.write("\r\n".getBytes());
146 211
         }
147 212
         // close the form
148 213
         byte[] end = ("--" + boundary + "--\r\n").getBytes();
149
-        sink.write(end);
150
-    }
151
-
152
-    /**
153
-     * Write octet stream data to request body
154
-     * @param sink
155
-     */
156
-    private void writeOctetData(BufferedSink sink) throws IOException {
157
-        // upload from storage
158
-        if (rawBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
159
-            String orgPath = rawBody.substring(RNFetchBlobConst.FILE_PREFIX.length());
160
-            orgPath = RNFetchBlobFS.normalizePath(orgPath);
161
-            // upload file from assets
162
-            if (RNFetchBlobFS.isAsset(orgPath)) {
163
-                try {
164
-                    String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
165
-                    contentLength = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
166
-                    requestStream = RNFetchBlob.RCTContext.getAssets().open(assetName);
167
-                } catch (IOException e) {
168
-//                        e.printStackTrace();
169
-                }
170
-            } else {
171
-                File f = new File(RNFetchBlobFS.normalizePath(orgPath));
172
-                try {
173
-                    if(!f.exists())
174
-                        f.createNewFile();
175
-                    contentLength = f.length();
176
-                    requestStream = new FileInputStream(f);
177
-                } catch (Exception e) {
178
-//                        callback.invoke(e.getLocalizedMessage(), null);
179
-                }
180
-            }
181
-        } else {
182
-            try {
183
-                byte[] bytes = Base64.decode(rawBody, 0);
184
-                contentLength = bytes.length;
185
-                requestStream = new ByteArrayInputStream(bytes);
186
-            } catch(Exception ex) {
187
-
188
-                Log.e("error", ex.getLocalizedMessage());
189
-            }
190
-        }
191
-        if(requestStream != null)
192
-            pipeStreamToSink(requestStream, sink);
193
-
214
+        os.write(end);
215
+        os.flush();
216
+        os.close();
217
+        return outputFile;
194 218
     }
195 219
 
196
-
197 220
 	/**
198 221
      * Write data to request body as-is
199 222
      * @param sink
200 223
      */
201 224
 	private void writeRawData(BufferedSink sink) throws IOException {
202
-		sink.write(rawBody.getBytes());
225
+        byte[] bytes = rawBody.getBytes();
226
+        contentLength = bytes.length;
227
+		sink.write(bytes);
203 228
 	}
204 229
 
205 230
     /**
@@ -210,27 +235,29 @@ public class RNFetchBlobBody extends RequestBody{
210 235
      */
211 236
     private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
212 237
         byte [] chunk = new byte[10240];
213
-        int read = stream.read(chunk, 0, 10240);
214
-        if(read > 0) {
215
-            sink.write(chunk, 0, read);
216
-        }
217
-        bytesWritten += read;
218
-        while(read > 0) {
219
-            read = stream.read(chunk, 0, 10240);
238
+        int read;
239
+        while((read = stream.read(chunk, 0, 10240)) > 0) {
220 240
             if(read > 0) {
221 241
                 sink.write(chunk, 0, read);
222
-                bytesWritten += read;
223
-                emitUploadProgress();
224 242
             }
225
-
226 243
         }
227 244
         stream.close();
228 245
     }
229 246
 
230
-    private void writeBufferToSink(byte [] bytes, BufferedSink sink) throws IOException {
231
-        bytesWritten += bytes.length;
232
-        sink.write(bytes);
233
-        emitUploadProgress();
247
+    /**
248
+     * Pipe input stream to a file
249
+     * @param is    The input stream
250
+     * @param os    The output stream to a file
251
+     * @throws IOException
252
+     */
253
+    private void pipeStreamToFileStream(InputStream is, FileOutputStream os) throws IOException {
254
+
255
+        byte[] buf = new byte[10240];
256
+        int len;
257
+        while ((len = is.read(buf)) > 0) {
258
+            os.write(buf, 0, len);
259
+        }
260
+        is.close();
234 261
     }
235 262
 
236 263
     /**
@@ -274,7 +301,7 @@ public class RNFetchBlobBody extends RequestBody{
274 301
             }
275 302
             // data field
276 303
             else {
277
-                total += field.data != null ? field.data.length() : 0;
304
+                total += field.data != null ? field.data.getBytes().length : 0;
278 305
             }
279 306
         }
280 307
         contentLength = total;

+ 1
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java View File

@@ -9,6 +9,7 @@ public class RNFetchBlobConst {
9 9
     public static final String EVENT_UPLOAD_PROGRESS = "RNFetchBlobProgress-upload";
10 10
     public static final String EVENT_PROGRESS = "RNFetchBlobProgress";
11 11
     public static final String EVENT_HTTP_STATE = "RNFetchBlobState";
12
+    public static final String EVENT_MESSAGE = "RNFetchBlobMessage";
12 13
     public static final String FILE_PREFIX = "RNFetchBlob-file://";
13 14
     public static final String FILE_PREFIX_BUNDLE_ASSET = "bundle-assets://";
14 15
     public static final String FILE_PREFIX_CONTENT = "content://";

+ 6
- 3
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java View File

@@ -251,11 +251,12 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
251 251
                     ));
252 252
                     break;
253 253
                 case Form:
254
+                    String boundary = "RNFetchBlob-" + taskId;
254 255
                     builder.method(method, new RNFetchBlobBody(
255 256
                             taskId,
256 257
                             requestType,
257 258
                             rawRequestBodyArray,
258
-                            MediaType.parse("multipart/form-data; boundary=RNFetchBlob-" + taskId)
259
+                            MediaType.parse("multipart/form-data; boundary="+ boundary)
259 260
                     ));
260 261
                     break;
261 262
 
@@ -301,7 +302,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
301 302
                         }
302 303
                         return originalResponse.newBuilder().body(extended).build();
303 304
                     } catch(Exception ex) {
304
-                        timeout = true;
305
+                        RNFetchBlobUtils.emitWarningEvent(ex.getLocalizedMessage());
305 306
                     }
306 307
                     return chain.proceed(chain.request());
307 308
                 }
@@ -439,7 +440,9 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
439 440
                     // It uses customized response body which is able to report download progress
440 441
                     // and write response data to destination path.
441 442
                     resp.body().bytes();
442
-                } catch (Exception ignored) {}
443
+                } catch (Exception ignored) {
444
+                    ignored.printStackTrace();
445
+                }
443 446
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath);
444 447
                 break;
445 448
             default:

+ 14
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java View File

@@ -1,5 +1,9 @@
1 1
 package com.RNFetchBlob;
2 2
 
3
+import com.facebook.react.bridge.Arguments;
4
+import com.facebook.react.bridge.WritableMap;
5
+import com.facebook.react.modules.core.DeviceEventManagerModule;
6
+
3 7
 import java.security.MessageDigest;
4 8
 import java.security.cert.CertificateException;
5 9
 
@@ -40,6 +44,16 @@ public class RNFetchBlobUtils {
40 44
 
41 45
     }
42 46
 
47
+    public static void emitWarningEvent(String data) {
48
+        WritableMap args = Arguments.createMap();
49
+        args.putString("event", "warn");
50
+        args.putString("detail", data);
51
+
52
+        // emit event to js context
53
+        RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
54
+                .emit(RNFetchBlobConst.EVENT_MESSAGE, args);
55
+    }
56
+
43 57
     public static OkHttpClient.Builder getUnsafeOkHttpClient() {
44 58
         try {
45 59
             // Create a trust manager that does not validate certificate chains