暂无描述

RNFetchBlobReq.java 20KB

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