facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
119.26k stars 24.34k forks source link

iOS - Image files included in outbound network requests are significantly larger than the original files #33760

Open emily-curry opened 2 years ago

emily-curry commented 2 years ago

Description

When creating a multipart/form-data request with a FormData object whose parts contain a uri property pointing to an image file, the resulting network request is much (~3-4 times) larger than the original image file.

This issue only occurs on iOS. This is a duplicate of #27099, which has been closed but is not fixed.

Version

0.67.3

Output of npx react-native info

System:
    OS: macOS 12.3.1
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 973.39 MB / 32.00 GB
    Shell: 3.3.1 - /usr/local/bin/fish
  Binaries:
    Node: 16.14.2 - /usr/local/bin/node
    Yarn: 1.22.17 - /usr/local/bin/yarn
    npm: 8.8.0 - /usr/local/bin/npm
    Watchman: 2022.03.21.00 - /usr/local/bin/watchman
  Managers:
    CocoaPods: 1.11.3 - /usr/local/bin/pod
  SDKs:
    iOS SDK:
      Platforms: DriverKit 21.4, iOS 15.4, macOS 12.3, tvOS 15.4, watchOS 8.5
    Android SDK:
      API Levels: 28, 29, 30, 31
      Build Tools: 28.0.3, 29.0.2, 29.0.3, 30.0.1, 30.0.2, 30.0.3, 31.0.0, 32.0.0, 32.1.0
      System Images: android-23 | Google APIs Intel x86 Atom_64, android-24 | Google APIs Intel x86 Atom, android-24 | Google Play Intel x86 Atom, android-26 | Google Play Intel x86 Atom, android-28 | Google Play Intel x86 Atom, android-29 | Google Play Intel x86 Atom, android-30 | Google Play Intel x86 Atom, android-31 | Google Play Intel x86 Atom_64
      Android NDK: Not Found
  IDEs:
    Android Studio: 2021.1 AI-211.7628.21.2111.8309675
    Xcode: 13.3.1/13E500a - /usr/bin/xcodebuild
  Languages:
    Java: 11.0.11 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 17.0.2 => 17.0.2
    react-native: 0.67.3 => 0.67.3
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found

Steps to reproduce

Obtain a URI to any on-disk image, call it fileUri. Create a FormData object with the file uri:

const formData = new FormData();
formData.append('file', { type: 'application/octet-stream', name: 'image.jpeg', uri: fileUri });

Then, make a request with that form data:

const response = await fetch('https://postman-echo.com/post', { method: 'POST', body: formData })

Observe the content-length of the request headers if you control the server, or in this example, observe the response body from postman-echo.

Snack, code example, screenshot, or link to a repository

Snack demonstrating issue (and current workaround): https://snack.expo.dev/@httpriestess/image-upload-size-repro

It posts an image file to postman-echo, and then displays the content-length header that postman-echo returns.

ntdiary commented 2 years ago

the issue https://github.com/facebook/react-native/issues/31641 is similar to here. Maybe that issue can be closed.

temporary solution

There are already several solutions in the https://github.com/facebook/react-native/issues/27099#issuecomment-1012775499 , such as copying the file and changing the image file extension or using rn-fetch-blob library ( I didn't verify ).

Here are another solution: customize a new handler to handle the request with file:// sheme and image type

add these two files to our own project. OriginImageRequestHandler.h OriginImageRequestHandler.mm

These two files just copy the contents of RCTFileRequestHandler and increase the priority to 4 ( RCTImageLoader is 2 ). We can customize canHandleRequest to handle the specified file if needed ( I only upload images in tmp directory ).

Image and Networking

Here are default handlers in RCTNetworking.mm:

084F468D-5919-4048-918D-94BF4975A9E2

I tested these two scenarios (use form-data to upload compressed jpeg/png image):

  1. There is no Image component in the app. RCTNetworking will use RCTFileRequestHandler to handle image file, the file size is same. Because _loaders in RCTImageLoader.mm is not initialized.
  2. There is Image component in the app. RCTNetworking will use RCTImageLoader sendRequest to handle image file, the file size is larger.

Of course, if these original images conform to the apple png/jpeg image storage format, the size will not become larger. ( e.g. upload an image saved using UIImagePNGRepresentation or UIImageJPEGRepresentation(image, 1.0) )

In addition. Here are some simplified call stacks for Image component (refrence)

  1. Static Image Resources ( e.g. require('./my-icon.png') ) the uri is resolved to http://localhost:8081/assets/src/my-icon.png?platform=ios&hash=0d42d44c5312fdb4a2e7d42a9bfa8b0c stack: RCTImageLoader --> RCTNetworking --> RCTHTTPRequestHandler.
  2. Xcode asset catalogs ( e.g. uri: 'app_icon') the uri is resolved to file:///xxx/xxx.png stack: RCTImageLoader --> RCTLocalAssetImageLoader
  3. Network Images, is same with Static Image Resources
  4. Uri Data Images this uri is data:data:image/png;base64,xxxx stack: RCTImageLoader --> RCTNetworking --> RCTDataRequestHandler.

related commits in history

f88bc3eb

The currently implemented handlers are: ...

  • RCTImageRequestHandler - a handler for loading local images from the iOS asset-library

this revision added file RCTImageRequestHandler.m to load local image with assets-library or ph sheme

36444a65

Add pluggable image processing system

this revision deleted RCTImageRequestHandler.m file and moved its function to the RCTImageLoader.m, added RCTAssetBundleImageLoader.m to load local image (Its current name is RCTLocalAssetImageLoader.mm)

f78526ce

Avoid re-encoding images when uploading local files (#31457)

this revision added a solution

e83feffe

Back out "Avoid re-encoding images when uploading local files" Summary: This was causing an upload error in FB Dating, will need to re-land with the fix.

this revision reverted f78526ce

Finally

If there is something wrong with the above, feel free to tell me. 🙂

chrisbobbe commented 1 year ago

Is there anything in the new architecture that we'd expect to fix this issue? I see this issue was marked with "Type: Old Architecture".

I think a good next step is to find out why the fix in #31457 was reverted, in e83feffe—I don't see any public discussion of that—and try to land an amended fix.

cortinico commented 1 year ago

Is there anything in the new architecture that we'd expect to fix this issue? I see this issue was marked with "Type: Old Architecture".

Not really as this is related to networking/images which is unrelated to the New Architecture at all

GiovanniVisentiniCasavo commented 1 year ago

this story is amazing.. A fix reverted for a dating app..

@ntdiary thanks for you solution, but I implemented it in a different way: in my opinion we can just define the handlerPriority in RCTFileRequestHandler, in this way you don't need to copy the file -> duplicate the code.. this handlerPriority looks to be used only in the network directory so I hope it is safe to do it.. By the way the images are display in the app, and the upload is correctly done..

here the patch:

diff --git a/node_modules/react-native/Libraries/Network/RCTFileRequestHandler.mm b/node_modules/react-native/Libraries/Network/RCTFileRequestHandler.mm
index 19d025c..519c860 100644
--- a/node_modules/react-native/Libraries/Network/RCTFileRequestHandler.mm
+++ b/node_modules/react-native/Libraries/Network/RCTFileRequestHandler.mm
@@ -92,6 +92,22 @@ - (void)cancelRequest:(NSOperation *)op
   return nullptr;
 }

+/**
+ * Add this function to give precedence to the file upload instead of image upload. the image upload is changing the file
+ * with no reason.
+ * this handlerPriority is used only in the RCTNetworking.mm and this file is in defined in the network directory so I hope is not used
+ * else where
+ * for the bug see:
+ * https://github.com/facebook/react-native/issues/27099
+ * https://github.com/facebook/react-native/pull/31457
+ * https://github.com/facebook/react-native/commit/e83feffeba567b4e304181632128ee1caa036c45
+ * https://github.com/facebook/react-native/issues/33760
+*/
+- (float)handlerPriority
+{
+  return 3;
+}
+
 @end

 Class RCTFileRequestHandlerCls(void)
ramble-lo commented 1 year ago

I don't know why it was work, but I think maybe this can help guys.

I have the same issue, but when I try to add the base64 into formData the issue was been solved.

from formData.append('file', { type: 'image/jpg', name: 'image.jpg', uri: fileUri });

to formData.append('file', { type: 'image/jpg', name: 'image.jpg', uri: fileUri, base64: '/9j/4AA.....' });

*I get these data by react-native-image-picker

mpoweredo commented 9 months ago

I don't know why it was work, but I think maybe this can help guys.

I have the same issue, but when I try to add the base64 into formData the issue was been solved.

from formData.append('file', { type: 'image/jpg', name: 'image.jpg', uri: fileUri });

to formData.append('file', { type: 'image/jpg', name: 'image.jpg', uri: fileUri, base64: '/9j/4AA.....' });

*I get these data by react-native-image-picker

Thanks buddy - looks like it is working right now! :) Passing base64 string through components and functions won't make app laggy?

react-native-bot commented 3 months ago

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

react-native-bot commented 2 months ago

This issue was closed because it has been stalled for 7 days with no activity.

stianjensen commented 1 month ago

This issue is not stale! We're still waiting for the fix that was backed out in https://github.com/facebook/react-native/commit/e83feffe to be re-landed or replaced with a new fix.

cortinico commented 1 month ago

Reopening as we believe this is still an issue.

Also related to: