暂无描述

RNFetchBlobReq.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. package com.RNFetchBlob;
  2. import android.app.DownloadManager;
  3. import android.content.BroadcastReceiver;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.content.IntentFilter;
  7. import android.database.Cursor;
  8. import android.net.Uri;
  9. import android.util.Base64;
  10. import android.util.Log;
  11. import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
  12. import com.RNFetchBlob.Response.RNFetchBlobFileResp;
  13. import com.facebook.react.bridge.Arguments;
  14. import com.facebook.react.bridge.Callback;
  15. import com.facebook.react.bridge.ReactApplicationContext;
  16. import com.facebook.react.bridge.ReadableArray;
  17. import com.facebook.react.bridge.ReadableMap;
  18. import com.facebook.react.bridge.ReadableMapKeySetIterator;
  19. import com.facebook.react.bridge.WritableMap;
  20. import com.facebook.react.modules.core.DeviceEventManagerModule;
  21. import java.io.ByteArrayInputStream;
  22. import java.io.File;
  23. import java.io.FileInputStream;
  24. import java.io.FileOutputStream;
  25. import java.io.IOException;
  26. import java.io.InputStream;
  27. import java.net.MalformedURLException;
  28. import java.net.SocketTimeoutException;
  29. import java.net.URL;
  30. import java.net.URLEncoder;
  31. import java.nio.ByteBuffer;
  32. import java.nio.CharBuffer;
  33. import java.nio.charset.CharacterCodingException;
  34. import java.nio.charset.Charset;
  35. import java.nio.charset.CharsetDecoder;
  36. import java.nio.charset.CharsetEncoder;
  37. import java.util.HashMap;
  38. import java.util.concurrent.TimeUnit;
  39. import okhttp3.Call;
  40. import okhttp3.ConnectionPool;
  41. import okhttp3.Headers;
  42. import okhttp3.Interceptor;
  43. import okhttp3.MediaType;
  44. import okhttp3.OkHttpClient;
  45. import okhttp3.Request;
  46. import okhttp3.RequestBody;
  47. import okhttp3.Response;
  48. import okhttp3.ResponseBody;
  49. import okhttp3.FormBody;
  50. import okhttp3.internal.framed.Header;
  51. import okhttp3.internal.http.OkHeaders;
  52. /**
  53. * Created by wkh237 on 2016/6/21.
  54. */
  55. public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
  56. enum RequestType {
  57. Form,
  58. SingleFile,
  59. AsIs,
  60. WithoutBody,
  61. Others
  62. };
  63. enum ResponseType {
  64. KeepInMemory,
  65. FileStorage
  66. };
  67. public static HashMap<String, Call> taskTable = new HashMap<>();
  68. static HashMap<String, Boolean> progressReport = new HashMap<>();
  69. static HashMap<String, Boolean> uploadProgressReport = new HashMap<>();
  70. static ConnectionPool pool = new ConnectionPool();
  71. MediaType contentType = RNFetchBlobConst.MIME_OCTET;
  72. ReactApplicationContext ctx;
  73. RNFetchBlobConfig options;
  74. String taskId;
  75. String method;
  76. String url;
  77. String rawRequestBody;
  78. String destPath;
  79. ReadableArray rawRequestBodyArray;
  80. ReadableMap headers;
  81. Callback callback;
  82. long contentLength;
  83. long downloadManagerId;
  84. RequestType requestType;
  85. ResponseType responseType;
  86. WritableMap respInfo;
  87. boolean timeout = false;
  88. public boolean reportProgress = false;
  89. public boolean reportUploadProgress = false;
  90. public RNFetchBlobReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, final Callback callback) {
  91. this.method = method;
  92. this.options = new RNFetchBlobConfig(options);
  93. this.taskId = taskId;
  94. this.url = url;
  95. this.headers = headers;
  96. this.callback = callback;
  97. this.rawRequestBody = body;
  98. this.rawRequestBodyArray = arrayBody;
  99. if(this.options.fileCache || this.options.path != null)
  100. responseType = ResponseType.FileStorage;
  101. else
  102. responseType = ResponseType.KeepInMemory;
  103. if (body != null)
  104. requestType = RequestType.SingleFile;
  105. else if (arrayBody != null)
  106. requestType = RequestType.Form;
  107. else
  108. requestType = RequestType.WithoutBody;
  109. }
  110. public static void cancelTask(String taskId) {
  111. if(taskTable.containsKey(taskId)) {
  112. Call call = taskTable.get(taskId);
  113. call.cancel();
  114. taskTable.remove(taskId);
  115. }
  116. }
  117. @Override
  118. public void run() {
  119. // use download manager instead of default HTTP implementation
  120. if (options.addAndroidDownloads != null && options.addAndroidDownloads.hasKey("useDownloadManager")) {
  121. if (options.addAndroidDownloads.getBoolean("useDownloadManager")) {
  122. Uri uri = Uri.parse(url);
  123. DownloadManager.Request req = new DownloadManager.Request(uri);
  124. req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
  125. if (options.addAndroidDownloads.hasKey("title")) {
  126. req.setTitle(options.addAndroidDownloads.getString("title"));
  127. }
  128. if (options.addAndroidDownloads.hasKey("description")) {
  129. req.setDescription(options.addAndroidDownloads.getString("description"));
  130. }
  131. // set headers
  132. ReadableMapKeySetIterator it = headers.keySetIterator();
  133. while (it.hasNextKey()) {
  134. String key = it.nextKey();
  135. req.addRequestHeader(key, headers.getString(key));
  136. }
  137. Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
  138. DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
  139. downloadManagerId = dm.enqueue(req);
  140. appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
  141. return;
  142. }
  143. }
  144. // find cached result if `key` property exists
  145. String cacheKey = this.taskId;
  146. String ext = this.options.appendExt != "" ? "." + this.options.appendExt : "";
  147. if (this.options.key != null) {
  148. cacheKey = RNFetchBlobUtils.getMD5(this.options.key);
  149. if (cacheKey == null) {
  150. cacheKey = this.taskId;
  151. }
  152. File file = new File(RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext);
  153. if (file.exists()) {
  154. callback.invoke(null, file.getAbsolutePath());
  155. return;
  156. }
  157. }
  158. if(this.options.path != null)
  159. this.destPath = this.options.path;
  160. else if(this.options.fileCache == true)
  161. this.destPath = RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext;
  162. OkHttpClient.Builder clientBuilder;
  163. try {
  164. // use trusty SSL socket
  165. if (this.options.trusty) {
  166. clientBuilder = RNFetchBlobUtils.getUnsafeOkHttpClient();
  167. } else {
  168. clientBuilder = new OkHttpClient.Builder();
  169. }
  170. final Request.Builder builder = new Request.Builder();
  171. try {
  172. builder.url(new URL(url));
  173. } catch (MalformedURLException e) {
  174. e.printStackTrace();
  175. }
  176. HashMap<String, String> mheaders = new HashMap<>();
  177. // set headers
  178. if (headers != null) {
  179. ReadableMapKeySetIterator it = headers.keySetIterator();
  180. while (it.hasNextKey()) {
  181. String key = it.nextKey();
  182. String value = headers.getString(key);
  183. builder.header(key, value);
  184. mheaders.put(key,value);
  185. }
  186. }
  187. if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put")) {
  188. String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase();
  189. if(cType == null) {
  190. builder.header("Content-Type", "application/octet-stream");
  191. requestType = RequestType.SingleFile;
  192. }
  193. if(rawRequestBody != null) {
  194. if(rawRequestBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
  195. requestType = RequestType.SingleFile;
  196. }
  197. else if (cType.toLowerCase().contains(";base64") || cType.toLowerCase().startsWith("application/octet")) {
  198. requestType = RequestType.SingleFile;
  199. } else {
  200. requestType = RequestType.AsIs;
  201. }
  202. }
  203. }
  204. else {
  205. requestType = RequestType.WithoutBody;
  206. }
  207. // set request body
  208. switch (requestType) {
  209. case SingleFile:
  210. builder.method(method, new RNFetchBlobBody(
  211. taskId,
  212. requestType,
  213. rawRequestBody,
  214. MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
  215. ));
  216. break;
  217. case AsIs:
  218. builder.method(method, new RNFetchBlobBody(
  219. taskId,
  220. requestType,
  221. rawRequestBody,
  222. MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
  223. ));
  224. break;
  225. case Form:
  226. builder.method(method, new RNFetchBlobBody(
  227. taskId,
  228. requestType,
  229. rawRequestBodyArray,
  230. MediaType.parse("multipart/form-data; boundary=RNFetchBlob-" + taskId)
  231. ));
  232. break;
  233. case WithoutBody:
  234. if(method.equalsIgnoreCase("POST") || method.equalsIgnoreCase("PUT"))
  235. {
  236. builder.method(method, RequestBody.create(null, new byte[0]));
  237. }
  238. else
  239. builder.method(method, null);
  240. break;
  241. }
  242. final Request req = builder.build();
  243. // Create response body depends on the responseType
  244. clientBuilder.addInterceptor(new Interceptor() {
  245. @Override
  246. public Response intercept(Chain chain) throws IOException {
  247. try {
  248. Response originalResponse = chain.proceed(req);
  249. ResponseBody extended;
  250. switch (responseType) {
  251. case KeepInMemory:
  252. extended = new RNFetchBlobDefaultResp(
  253. RNFetchBlob.RCTContext,
  254. taskId,
  255. originalResponse.body());
  256. break;
  257. case FileStorage:
  258. extended = new RNFetchBlobFileResp(
  259. RNFetchBlob.RCTContext,
  260. taskId,
  261. originalResponse.body(),
  262. destPath);
  263. break;
  264. default:
  265. extended = new RNFetchBlobDefaultResp(
  266. RNFetchBlob.RCTContext,
  267. taskId,
  268. originalResponse.body());
  269. break;
  270. }
  271. return originalResponse.newBuilder().body(extended).build();
  272. } catch(Exception ex) {
  273. timeout = true;
  274. }
  275. return chain.proceed(chain.request());
  276. }
  277. });
  278. if(options.timeout > 0) {
  279. clientBuilder.connectTimeout(options.timeout, TimeUnit.MILLISECONDS);
  280. clientBuilder.readTimeout(options.timeout, TimeUnit.MILLISECONDS);
  281. }
  282. clientBuilder.connectionPool(pool);
  283. clientBuilder.retryOnConnectionFailure(false);
  284. clientBuilder.followRedirects(true);
  285. OkHttpClient client = clientBuilder.build();
  286. Call call = client.newCall(req);
  287. taskTable.put(taskId, call);
  288. call.enqueue(new okhttp3.Callback() {
  289. @Override
  290. public void onFailure(Call call, IOException e) {
  291. cancelTask(taskId);
  292. if(respInfo == null) {
  293. respInfo = Arguments.createMap();
  294. }
  295. // check if this error caused by socket timeout
  296. if(e.getClass().equals(SocketTimeoutException.class)) {
  297. respInfo.putBoolean("timeout", true);
  298. callback.invoke("request timed out.", respInfo, null);
  299. }
  300. else
  301. callback.invoke(e.getLocalizedMessage(), respInfo, null);
  302. removeTaskInfo();
  303. }
  304. @Override
  305. public void onResponse(Call call, Response response) throws IOException {
  306. ReadableMap notifyConfig = options.addAndroidDownloads;
  307. // Download manager settings
  308. if(notifyConfig != null ) {
  309. String title = "", desc = "", mime = "text/plain";
  310. boolean scannable = false, notification = false;
  311. if(notifyConfig.hasKey("title"))
  312. title = options.addAndroidDownloads.getString("title");
  313. if(notifyConfig.hasKey("description"))
  314. desc = notifyConfig.getString("description");
  315. if(notifyConfig.hasKey("mime"))
  316. mime = notifyConfig.getString("mime");
  317. if(notifyConfig.hasKey("mediaScannable"))
  318. scannable = notifyConfig.getBoolean("mediaScannable");
  319. if(notifyConfig.hasKey("notification"))
  320. notification = notifyConfig.getBoolean("notification");
  321. DownloadManager dm = (DownloadManager)RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE);
  322. dm.addCompletedDownload(title, desc, scannable, mime, destPath, contentLength, notification);
  323. }
  324. done(response);
  325. }
  326. });
  327. } catch (Exception error) {
  328. error.printStackTrace();
  329. taskTable.remove(taskId);
  330. callback.invoke("RNFetchBlob request error: " + error.getMessage() + error.getCause());
  331. }
  332. }
  333. /**
  334. * Remove cached information of the HTTP task
  335. */
  336. private void removeTaskInfo() {
  337. if(taskTable.containsKey(taskId))
  338. taskTable.remove(taskId);
  339. if(uploadProgressReport.containsKey(taskId))
  340. uploadProgressReport.remove(taskId);
  341. if(progressReport.containsKey(taskId))
  342. progressReport.remove(taskId);
  343. }
  344. /**
  345. * Send response data back to javascript context.
  346. * @param resp OkHttp response object
  347. */
  348. private void done(Response resp) {
  349. boolean isBlobResp = isBlobResponse(resp);
  350. emitStateEvent(getResponseInfo(resp, isBlobResp));
  351. switch (responseType) {
  352. case KeepInMemory:
  353. try {
  354. // For XMLHttpRequest, automatic response data storing strategy, when response
  355. // header is not `application/json` or `text/plain`, write response data to
  356. // file system.
  357. if(isBlobResp && options.auto == true) {
  358. String dest = RNFetchBlobFS.getTmpPath(ctx, taskId);
  359. InputStream ins = resp.body().byteStream();
  360. FileOutputStream os = new FileOutputStream(new File(dest));
  361. byte [] buffer = new byte[10240];
  362. int read = ins.read(buffer);
  363. os.write(buffer,0,read);
  364. while(read > 0) {
  365. os.write(buffer,0,read);
  366. read = ins.read(buffer);
  367. }
  368. ins.close();
  369. os.close();
  370. callback.invoke(null, null, dest);
  371. }
  372. else {
  373. // #73 Check if the response data contains valid UTF8 string, since BASE64
  374. // encoding will somehow break the UTF8 string format, to encode UTF8
  375. // string correctly, we should do URL encoding before BASE64.
  376. String utf8Str;
  377. byte[] b = resp.body().bytes();
  378. CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
  379. try {
  380. encoder.encode(ByteBuffer.wrap(b).asCharBuffer());
  381. // if the data can be encoded to UTF8 append URL encode
  382. b = URLEncoder.encode(new String(b), "UTF-8").replace("+", "%20").getBytes();
  383. }
  384. // This usually mean the data is binary data
  385. catch(CharacterCodingException e) {
  386. }
  387. finally {
  388. callback.invoke(null, null, android.util.Base64.encodeToString(b, Base64.NO_WRAP));
  389. }
  390. }
  391. } catch (IOException e) {
  392. callback.invoke("RNFetchBlob failed to encode response data to BASE64 string.", null);
  393. }
  394. break;
  395. case FileStorage:
  396. try{
  397. // In order to write response data to `destPath` we have to invoke this method.
  398. // It uses customized response body which is able to report download progress
  399. // and write response data to destination path.
  400. resp.body().bytes();
  401. } catch (Exception ignored) {
  402. }
  403. callback.invoke(null, null, this.destPath);
  404. break;
  405. default:
  406. try {
  407. callback.invoke(null, null, new String(resp.body().bytes(), "UTF-8"));
  408. } catch (IOException e) {
  409. callback.invoke("RNFetchBlob failed to encode response data to UTF8 string.", null);
  410. }
  411. break;
  412. }
  413. removeTaskInfo();
  414. }
  415. /**
  416. * Invoke this method to enable download progress reporting.
  417. * @param taskId Task ID of the HTTP task.
  418. * @return Task ID of the target task
  419. */
  420. public static boolean isReportProgress(String taskId) {
  421. if(!progressReport.containsKey(taskId)) return false;
  422. return progressReport.get(taskId);
  423. }
  424. /**
  425. * Invoke this method to enable download progress reporting.
  426. * @param taskId Task ID of the HTTP task.
  427. * @return Task ID of the target task
  428. */
  429. public static boolean isReportUploadProgress(String taskId) {
  430. if(!uploadProgressReport.containsKey(taskId)) return false;
  431. return uploadProgressReport.get(taskId);
  432. }
  433. private WritableMap getResponseInfo(Response resp, boolean isBlobResp) {
  434. WritableMap info = Arguments.createMap();
  435. info.putInt("status", resp.code());
  436. info.putString("state", "2");
  437. info.putString("taskId", this.taskId);
  438. info.putBoolean("timeout", timeout);
  439. WritableMap headers = Arguments.createMap();
  440. for(int i =0;i< resp.headers().size();i++) {
  441. headers.putString(resp.headers().name(i), resp.headers().value(i));
  442. }
  443. info.putMap("headers", headers);
  444. Headers h = resp.headers();
  445. if(isBlobResp) {
  446. info.putString("respType", "blob");
  447. }
  448. else if(getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("text/")) {
  449. info.putString("respType", "text");
  450. }
  451. else if(getHeaderIgnoreCases(h, "content-type").contains("application/json")) {
  452. info.putString("respType", "json");
  453. }
  454. else {
  455. info.putString("respType", "");
  456. }
  457. return info;
  458. }
  459. private boolean isBlobResponse(Response resp) {
  460. Headers h = resp.headers();
  461. String ctype = getHeaderIgnoreCases(h, "Content-Type");
  462. boolean isText = !ctype.equalsIgnoreCase("text/");
  463. boolean isJSON = !ctype.equalsIgnoreCase("application/json");
  464. boolean isCustomBinary = false;
  465. if(options.binaryContentTypes != null) {
  466. for(int i = 0; i< options.binaryContentTypes.size();i++) {
  467. if(ctype.toLowerCase().contains(options.binaryContentTypes.getString(i).toLowerCase())) {
  468. isCustomBinary = true;
  469. break;
  470. }
  471. }
  472. }
  473. return (!(isJSON || isText)) || isCustomBinary;
  474. }
  475. private String getHeaderIgnoreCases(Headers headers, String field) {
  476. String val = headers.get(field);
  477. if(val != null) return val;
  478. return headers.get(field.toLowerCase()) == null ? "" : headers.get(field.toLowerCase());
  479. }
  480. private String getHeaderIgnoreCases(HashMap<String,String> headers, String field) {
  481. String val = headers.get(field);
  482. if(val != null) return val;
  483. return headers.get(field.toLowerCase()) == null ? "" : headers.get(field.toLowerCase());
  484. }
  485. private void emitStateEvent(WritableMap args) {
  486. RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
  487. .emit(RNFetchBlobConst.EVENT_HTTP_STATE, args);
  488. }
  489. @Override
  490. public void onReceive(Context context, Intent intent) {
  491. String action = intent.getAction();
  492. if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
  493. Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
  494. long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
  495. if (id == this.downloadManagerId) {
  496. DownloadManager.Query query = new DownloadManager.Query();
  497. query.setFilterById(downloadManagerId);
  498. DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
  499. dm.query(query);
  500. Cursor c = dm.query(query);
  501. if (c.moveToFirst()) {
  502. String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
  503. Uri uri = Uri.parse(contentUri);
  504. Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Images.ImageColumns.DATA}, null, null, null);
  505. if (cursor != null) {
  506. cursor.moveToFirst();
  507. String filePath = cursor.getString(0);
  508. cursor.close();
  509. this.callback.invoke(null, null, filePath);
  510. }
  511. else
  512. this.callback.invoke(null, null, null);
  513. }
  514. }
  515. }
  516. }
  517. }