Ei kuvausta

RNFetchBlobReq.java 21KB

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