Repository navigation
feat: add sendFile() and receiveHttpBodyToFile() to stream files natively - #242
Open
HugoGresse wants to merge 1 commit into
Open
HugoGresse wants to merge 1 commit into
HugoGresse wants to merge 1 commit into
Conversation
…vely Sending a file with write() moves every chunk through JS as base64, which caps a LAN transfer at 1-2 MB/s. Two new Socket methods keep the bytes on the native side: - sendFile(path, offset, length) streams a range of a file to the socket. - receiveHttpBodyToFile(path) writes the body of an HTTP response to a file, and appends to it on a 206 so a download can be resumed. Both emit 'fileProgress', and the receiver ends with 'fileEnd'. On iOS, the socket delegate callbacks and the module methods now share a private serial queue instead of the main queue, so that the file is not read and written on the main thread. Related to Rapsssito#212
HugoGresse
force-pushed
the
feat/native-file-streaming
branch
from
September 30, 2026 16:56
20fbe7f to
1ae084d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two
Socketmethods that move a file between the disk and the socket on the native side, so its bytes never go through JS:socket.sendFile(path, offset, length)streams a range of a file to the socket. It returns aPromise.socket.receiveHttpBodyToFile(path)writes the body of an HTTP response to a file. A206response is appended to the file, so an interrupted download can be resumed with aRangerequest.'fileProgress'(bytes, total)on both sides, and'fileEnd'(bytes, error)on the receiving side.Why: with
write()and'data', every chunk crosses the bridge as base64. Between two phones on the same Wi-Fi we could not get past 2 MB/s that way (same observation as #212). With these two methods the same transfer runs at 13 to 57 MB/s, and memory use no longer depends on the file size.Related to #212.
Changes
JS (
src/Socket.js, types regenerated withyarn declaration:build)sendFile()uses the samewrittenmessage ids aswrite(), so it is ordered after the pending writes. It rejects on awrittenerror, and also on'error'/'close', because iOS only reports a failed send as an error event.'fileProgress'resets the idle timeout set withsetTimeout(): no'data'event is emitted during a native transfer.Android
TcpSocketClient.sendFile()runs on the existing write executor.HttpBodyFileReceiver: parses the response header, then writes the body to the file. The receiver task hands it the bytes instead of emittingdataevents.dataevents keep their 16 KB chunks. The 64 KB read size is only used oncereceiveHttpBodyToFile()was called.iOS / macOS
sendFilereads the file in 256 KB chunks and queues the next one fromdidWriteDataWithTag, so at most one chunk is in memory.NSFileHandlemethods that report anNSErrorneed iOS 13 / macOS 10.15. As the podspec declares iOS 9 and macOS 10.14, they are behind@available, with the older methods as a fallback.methodQueue). As a side effect,_clientsis no longer touched from two queues.Docs: README, API section (
Socket.sendFile(),Socket.receiveHttpBodyToFile(), the two events).Tests
yarn cipasses. 10 new Jest tests in__tests__/fileTransfer.test.js(20 in total): arguments sent to the native module, ordering withwrite(), the three rejection paths ofsendFile(), event filtering by socket id,fileEndwith and without an error.206was not exercised on phones, only in the harnesses below.NSFileHandlefallback, the extra rejections insendFile(), and the read size kept at 16 KB fordataevents. This exact version was not run again on phones. What I ran instead:javacagainstandroid.jarand React Native 0.86.clang -fsyntax-onlyfor an iOS 12 target,-Wall -Wunguarded-availability: no warning.TcpSocketClientclasses (Java on the JVM, Objective-C on macOS with CocoaAsyncSocket), not included in this PR. Cases:200,206appended to a partial file,404, connection closed beforeContent-Length, noContent-Length, missing source file, and a plain socket still receivingdataevents. A 25 MB file is compared byte for byte. The Objective-C harness passes with bothNSFileHandlecode paths.Limits and open questions
receiveHttpBodyToFile()is tied to HTTP, which is unusual for this library. The reason is that the response header and the first bytes of the body arrive in the same read: if JS parsed the header and then asked the native side to switch to a file, somedataevents would already be on their way. I am happy to rename it or to change its shape if you prefer another API.Transfer-Encoding: chunked, no decompression, one response per socket. The size is only bounded byContent-Length.write()issued whilesendFile()is still running can end up in the middle of the file. The README says to wait for the promise.