wkh237 / react-native-fetch-blob

A project committed to making file access and data transfer easier, efficient for React Native developers.
MIT License
2.61k stars 1.59k forks source link

Possible Inefficiency/issues with uploading document provider provided documents #525

Open dantman opened 7 years ago

dantman commented 7 years ago

I've been reviewing the code to see if RNFB is viable for my use case, I've got a document picker on Android (some native code that uses Intent.ACTION_GET_CONTENT to let the user select a file and then returns the content:// URI provided by the document provider) and I need to upload that document to our API.

From what I see in PathResolver.java, RNFetchBlobBody.java, and RNFetchBlobReq.java it looks like what RNFB does to handle a document provided by a document provider is hacky/inefficient.

The PathResolver. getRealPathFromURI is called through normalizePath by a bunch of methods that just want to get an InputStream (readFile to read it, various parts of the upload code to upload the data from that stream). To deal with document providers it first skips the standard way of doing things on Android and hardcodes patterns of a few different document providers manipulating the Uris returned by document providers like external stores, file content, google photos, and media provider in order to return something closer to a Filesystem path. If it's not one of them then it uses openInputStream and getCacheDir to copy the document provided by the document provider to a temporary file (which may involve a network download that doesn't have its progress tracked and blocks the upload; I'm not certain but it almost looks like it might block one of the threads to do this; and may be padding the size of the file to an incorrect length) and it returns the path to that file.

The upload code calling that method then uses the path to open an InputStream to that file and uploads from the stream.

So in short:

I think it would be more reasonable to have a different method to PathResolver.getRealPathFromURI that instead returns an InputStream directly and doesn't use the path hacks. Methods like readFile and the upload body code would call that directly instead of trying to get a path.

Then filesystem files would come from the document provider provided stream and if its from a custom or remote document provider that data would stream into the upload as it is being generated or downloaded and be directly reflected in the upload progress provided by RNFS.

dantman commented 7 years ago

One possible reason the library may be writing to a temporary file first is because the Content-Length needs to be defined, and file size might not be immediately available for custom providers, so the library just doesn't bother with passing around file lengths and just copies the file to the filesystem so it can get a file based input stream. However the way the library does this is wrong in multiple ways for every method you could get a file.

To get the Content-Length this library seems to use inputStream.available() to set the Content-Length. The point of copying to a File appears to be to try and get .available() to return the length of the file instead of just what can be read currently. Except .available() is NOT the actual file length, it's an estimate of how much can be read without blocking and should never be used this way.

https://developer.android.com/reference/java/io/InputStream.html#available()

Note that while some implementations of InputStream will return the total number of bytes in the stream, many will not. It is never correct to use the return value of this method to allocate a buffer intended to hold all data in this stream.

Then after copying the document to the temporary file, for multipart the RNFetchBlobBody copies the data from the temp file to another temp file it uses to store the multipart body, while building headers using inefficient string concatenation open to injection attacks/corruption instead of the multipart builder.

TL;DR this library is handling files on Android in discouraged and unreliable ways and using inefficient hacks to stop the library from breaking. I'm definitely going to have to overhaul File related portions of this library if it will be at all usable.

The proper way to handle files and documents:

lll000111 commented 7 years ago

Just an aside, you may want to also fork and clone https://github.com/wkh237/react-native-fetch-blob-dev

Plus my still open PR for that repo: https://github.com/wkh237/react-native-fetch-blob-dev/pull/8

Tests are in ./test/

The main test file is ./test-init.js - it's best to only enable one or two tests or it's too much all at once. The per-release test files each test a feature introduced with that release.

A patch should pass the relevant tests, or if not, the API change should be documented.

If you have trouble setting it up you may find something in the closed issues of that repo, there are one or two minor issues where the documentation is outdated. I had planned to patch the README too but since my PR still is not merged I forgot about it.

Overall I found it pretty easy to set this up and run the tests though. If you need help setting it up I'll probably be able to help — unlike with the "fetch" part of RNFB, since I will only touch the filesystem related code.

serhiiharbo commented 7 years ago

We got a crash on production related to this issue.

lll000111 commented 7 years ago

@aquile

I just use my own fork of the 0.10.9 tree. Not a big deal?

Anyway, the package owner does not seem to have the time, clearly more help is needed. You can ask him or just maintain your own fork and merge back without time pressure on yourself.

That the situation is unsatisfactory is a general problem with ALL open source packages. Github and Co. don't help much with a professionalization — which would require a major effort of monetization, e.g. a subscription fee for all npm packages and maintainers are paid by some agreed-upon key out of that budget. The "it's free!" only goes so far.

lll000111 commented 6 years ago

@dantman If this is still relevant, the package has a new maintainer, see the top level README, so there possibly is a chance of getting something done.

Traviskn commented 6 years ago

We'd love to see any PR's over at the new repo location! https://github.com/joltup/react-native-fetch-blob