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
 import java.io.ByteArrayInputStream;
13
 import java.io.ByteArrayInputStream;
14
 import java.io.File;
14
 import java.io.File;
15
 import java.io.FileInputStream;
15
 import java.io.FileInputStream;
16
+import java.io.FileNotFoundException;
17
+import java.io.FileOutputStream;
16
 import java.io.IOException;
18
 import java.io.IOException;
17
 import java.io.InputStream;
19
 import java.io.InputStream;
20
+import java.nio.ByteBuffer;
21
+import java.nio.MappedByteBuffer;
18
 import java.util.ArrayList;
22
 import java.util.ArrayList;
19
 import java.util.HashMap;
23
 import java.util.HashMap;
20
 
24
 
33
 public class RNFetchBlobBody extends RequestBody{
37
 public class RNFetchBlobBody extends RequestBody{
34
 
38
 
35
     InputStream requestStream;
39
     InputStream requestStream;
36
-    long contentLength;
40
+    long contentLength = 0;
37
     long bytesWritten = 0;
41
     long bytesWritten = 0;
38
     ReadableArray form;
42
     ReadableArray form;
39
     String mTaskId;
43
     String mTaskId;
40
     String rawBody;
44
     String rawBody;
41
     RNFetchBlobReq.RequestType requestType;
45
     RNFetchBlobReq.RequestType requestType;
42
     MediaType mime;
46
     MediaType mime;
47
+    File bodyCache;
48
+
43
 
49
 
44
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, ReadableArray form, MediaType contentType) {
50
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, ReadableArray form, MediaType contentType) {
45
         this.mTaskId = taskId;
51
         this.mTaskId = taskId;
46
         this.form = form;
52
         this.form = form;
47
         requestType = type;
53
         requestType = type;
48
         mime = contentType;
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
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, String rawBody, MediaType contentType) {
63
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, String rawBody, MediaType contentType) {
55
         mime = contentType;
67
         mime = contentType;
56
     }
68
     }
57
 
69
 
70
+    @Override
71
+    public long contentLength() {
72
+        return contentLength;
73
+    }
58
     @Override
74
     @Override
59
     public MediaType contentType() {
75
     public MediaType contentType() {
60
         return mime;
76
         return mime;
67
         BufferedSink buffer = Okio.buffer(source);
83
         BufferedSink buffer = Okio.buffer(source);
68
         switch (requestType) {
84
         switch (requestType) {
69
             case Form:
85
             case Form:
70
-                writeFormData(sink);
86
+                pipeStreamToSink(new FileInputStream(bodyCache), sink);
71
                 break;
87
                 break;
72
             case SingleFile:
88
             case SingleFile:
73
-                writeOctetData(sink);
89
+                if(requestStream != null)
90
+                    pipeStreamToSink(requestStream, sink);
74
                 break;
91
                 break;
75
             case AsIs:
92
             case AsIs:
76
 				writeRawData(sink);
93
 				writeRawData(sink);
79
         buffer.flush();
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
         String boundary = "RNFetchBlob-" + mTaskId;
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
         ArrayList<FormField> fields = countFormDataLength();
148
         ArrayList<FormField> fields = countFormDataLength();
85
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
149
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
150
+
86
         for(int i = 0;i < fields.size(); i++) {
151
         for(int i = 0;i < fields.size(); i++) {
87
             FormField field = fields.get(i);
152
             FormField field = fields.get(i);
88
             String data = field.data;
153
             String data = field.data;
95
             if (field.filename != null) {
160
             if (field.filename != null) {
96
                 header += "Content-Disposition: form-data; name=" + name + "; filename=" + field.filename + "\r\n";
161
                 header += "Content-Disposition: form-data; name=" + name + "; filename=" + field.filename + "\r\n";
97
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
162
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
98
-                sink.write(header.getBytes());
163
+                os.write(header.getBytes());
99
                 // file field header end
164
                 // file field header end
100
                 // upload from storage
165
                 // upload from storage
101
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
166
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
106
                         try {
171
                         try {
107
                             String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
172
                             String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
108
                             InputStream in = ctx.getAssets().open(assetName);
173
                             InputStream in = ctx.getAssets().open(assetName);
109
-                            pipeStreamToSink(in, sink);
174
+                            pipeStreamToFileStream(in, os);
110
                         } catch (IOException e) {
175
                         } catch (IOException e) {
111
                             Log.e("RNFetchBlob", "Failed to create form data asset :" + orgPath + ", " + e.getLocalizedMessage() );
176
                             Log.e("RNFetchBlob", "Failed to create form data asset :" + orgPath + ", " + e.getLocalizedMessage() );
112
                         }
177
                         }
116
                         File file = new File(RNFetchBlobFS.normalizePath(orgPath));
181
                         File file = new File(RNFetchBlobFS.normalizePath(orgPath));
117
                         if(file.exists()) {
182
                         if(file.exists()) {
118
                             FileInputStream fs = new FileInputStream(file);
183
                             FileInputStream fs = new FileInputStream(file);
119
-                            pipeStreamToSink(fs, sink);
184
+                            pipeStreamToFileStream(fs, os);
120
                         }
185
                         }
121
                         else {
186
                         else {
122
                             Log.e("RNFetchBlob", "Failed to create form data from path :" + orgPath + "file not exists.");
187
                             Log.e("RNFetchBlob", "Failed to create form data from path :" + orgPath + "file not exists.");
126
                 // base64 embedded file content
191
                 // base64 embedded file content
127
                 else {
192
                 else {
128
                     byte[] b = Base64.decode(data, 0);
193
                     byte[] b = Base64.decode(data, 0);
129
-                    sink.write(b);
194
+                    os.write(b);
130
                     bytesWritten += b.length;
195
                     bytesWritten += b.length;
131
                     emitUploadProgress();
196
                     emitUploadProgress();
132
                 }
197
                 }
136
             else {
201
             else {
137
                 header += "Content-Disposition: form-data; name=" + name + "\r\n";
202
                 header += "Content-Disposition: form-data; name=" + name + "\r\n";
138
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
203
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
139
-                sink.write(header.getBytes());
204
+                os.write(header.getBytes());
140
                 byte[] fieldData = field.data.getBytes();
205
                 byte[] fieldData = field.data.getBytes();
141
                 bytesWritten += fieldData.length;
206
                 bytesWritten += fieldData.length;
142
-                sink.write(fieldData);
207
+                os.write(fieldData);
143
             }
208
             }
144
             // form end
209
             // form end
145
-            sink.write("\r\n".getBytes());
210
+            os.write("\r\n".getBytes());
146
         }
211
         }
147
         // close the form
212
         // close the form
148
         byte[] end = ("--" + boundary + "--\r\n").getBytes();
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
      * Write data to request body as-is
221
      * Write data to request body as-is
199
      * @param sink
222
      * @param sink
200
      */
223
      */
201
 	private void writeRawData(BufferedSink sink) throws IOException {
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
      */
235
      */
211
     private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
236
     private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
212
         byte [] chunk = new byte[10240];
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
             if(read > 0) {
240
             if(read > 0) {
221
                 sink.write(chunk, 0, read);
241
                 sink.write(chunk, 0, read);
222
-                bytesWritten += read;
223
-                emitUploadProgress();
224
             }
242
             }
225
-
226
         }
243
         }
227
         stream.close();
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
             }
301
             }
275
             // data field
302
             // data field
276
             else {
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
         contentLength = total;
307
         contentLength = total;

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

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

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

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

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

1
 package com.RNFetchBlob;
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
 import java.security.MessageDigest;
7
 import java.security.MessageDigest;
4
 import java.security.cert.CertificateException;
8
 import java.security.cert.CertificateException;
5
 
9
 
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
     public static OkHttpClient.Builder getUnsafeOkHttpClient() {
57
     public static OkHttpClient.Builder getUnsafeOkHttpClient() {
44
         try {
58
         try {
45
             // Create a trust manager that does not validate certificate chains
59
             // Create a trust manager that does not validate certificate chains