mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
457d14bd1b
Summary: When an XMLHttpRequest is performed, the `onprogress` event it is not invoked when the `Content-Length` header is missing in the response. This is the case when we are calling an endpoint that responds with `transfer-encoding: chunked` (https://tools.ietf.org/html/rfc9112#section-7.1), preventing the user to keep track of the progress while the server is sending chunks. Despite we will never know the total length of the content (because it will not be known due to the RFC specification, so it will be always `-1`), we will now be able to keep track of the loaded data. Note that in Android, this is the current default behaviour. To address this issue: - I removed the condition where the `downloadProgressBlock` was dispatched only when `response.expectedContentLength` was greater than 0 - I created a new test case for `XMLHttpRequest` in the tester app to download a chunked file ## Changelog: [IOS] [CHANGED] - fire `onprogress` event for `XMLHttpRequest` even when the `Content-Length` header is missing in the response headers Pull Request resolved: https://github.com/facebook/react-native/pull/44899 Test Plan: |before|after| |----------|:-------------:| |https://github.com/facebook/react-native/assets/37150312/6da3518f-eed3-4808-a2f8-abe26e5c7487|https://github.com/facebook/react-native/assets/37150312/ed1da300-dcf7-4874-a941-a2289f1cb777 Reviewed By: cortinico Differential Revision: D58562088 Pulled By: NickGerleman fbshipit-source-id: 23a1cafa49ddcd25fa0db7d04fae845126771425
224 lines
5.4 KiB
Plaintext
224 lines
5.4 KiB
Plaintext
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import <mutex>
|
|
|
|
#import <React/RCTLog.h>
|
|
#import <React/RCTNetworkTask.h>
|
|
#import <React/RCTUtils.h>
|
|
|
|
@implementation RCTNetworkTask {
|
|
NSMutableData *_data;
|
|
id<RCTURLRequestHandler> _handler;
|
|
dispatch_queue_t _callbackQueue;
|
|
std::mutex _mutex;
|
|
|
|
RCTNetworkTask *_selfReference;
|
|
}
|
|
|
|
- (instancetype)initWithRequest:(NSURLRequest *)request
|
|
handler:(id<RCTURLRequestHandler>)handler
|
|
callbackQueue:(dispatch_queue_t)callbackQueue
|
|
{
|
|
RCTAssertParam(request);
|
|
RCTAssertParam(handler);
|
|
RCTAssertParam(callbackQueue);
|
|
|
|
static NSUInteger requestID = 0;
|
|
|
|
if ((self = [super init])) {
|
|
_requestID = @(requestID++);
|
|
_request = request;
|
|
_handler = handler;
|
|
_callbackQueue = callbackQueue;
|
|
_status = RCTNetworkTaskPending;
|
|
|
|
dispatch_queue_set_specific(callbackQueue, (__bridge void *)self, (__bridge void *)self, NULL);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
|
|
|
- (void)invalidate
|
|
{
|
|
_selfReference = nil;
|
|
_completionBlock = nil;
|
|
_downloadProgressBlock = nil;
|
|
_incrementalDataBlock = nil;
|
|
_responseBlock = nil;
|
|
_uploadProgressBlock = nil;
|
|
_requestToken = nil;
|
|
}
|
|
|
|
- (void)dispatchCallback:(dispatch_block_t)callback
|
|
{
|
|
if (dispatch_get_specific((__bridge void *)self) == (__bridge void *)self) {
|
|
callback();
|
|
} else {
|
|
dispatch_async(_callbackQueue, callback);
|
|
}
|
|
}
|
|
|
|
- (void)start
|
|
{
|
|
if (_status != RCTNetworkTaskPending) {
|
|
RCTLogError(@"RCTNetworkTask was already started or completed");
|
|
return;
|
|
}
|
|
|
|
if (_requestToken == nil) {
|
|
id token = [_handler sendRequest:_request withDelegate:self];
|
|
if ([self validateRequestToken:token]) {
|
|
_selfReference = self;
|
|
_status = RCTNetworkTaskInProgress;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)cancel
|
|
{
|
|
if (_status == RCTNetworkTaskFinished) {
|
|
return;
|
|
}
|
|
|
|
_status = RCTNetworkTaskFinished;
|
|
id token = _requestToken;
|
|
if (token && [_handler respondsToSelector:@selector(cancelRequest:)]) {
|
|
[_handler cancelRequest:token];
|
|
}
|
|
[self invalidate];
|
|
}
|
|
|
|
- (BOOL)validateRequestToken:(id)requestToken
|
|
{
|
|
BOOL valid = YES;
|
|
if (_requestToken == nil) {
|
|
_requestToken = requestToken;
|
|
} else if (![requestToken isEqual:_requestToken]) {
|
|
if (RCT_DEBUG) {
|
|
RCTLogError(@"Unrecognized request token: %@ expected: %@", requestToken, _requestToken);
|
|
}
|
|
valid = NO;
|
|
}
|
|
|
|
if (!valid) {
|
|
_status = RCTNetworkTaskFinished;
|
|
if (_completionBlock) {
|
|
RCTURLRequestCompletionBlock completionBlock = _completionBlock;
|
|
[self dispatchCallback:^{
|
|
completionBlock(self->_response, nil, RCTErrorWithMessage(@"Invalid request token."));
|
|
}];
|
|
}
|
|
[self invalidate];
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
- (void)URLRequest:(id)requestToken didSendDataWithProgress:(int64_t)bytesSent
|
|
{
|
|
if (![self validateRequestToken:requestToken]) {
|
|
return;
|
|
}
|
|
|
|
if (_uploadProgressBlock) {
|
|
RCTURLRequestProgressBlock uploadProgressBlock = _uploadProgressBlock;
|
|
int64_t length = _request.HTTPBody.length;
|
|
[self dispatchCallback:^{
|
|
uploadProgressBlock(bytesSent, length);
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
|
|
{
|
|
if (![self validateRequestToken:requestToken]) {
|
|
return;
|
|
}
|
|
|
|
_response = response;
|
|
if (_responseBlock) {
|
|
RCTURLRequestResponseBlock responseBlock = _responseBlock;
|
|
[self dispatchCallback:^{
|
|
responseBlock(response);
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data
|
|
{
|
|
if (![self validateRequestToken:requestToken]) {
|
|
return;
|
|
}
|
|
|
|
int64_t length = 0;
|
|
|
|
{
|
|
// NSData is not thread-safe and this method could be called from different threads as
|
|
// RCTURLRequestHandlers does not provide any guarantee of which thread we are called on.
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
if (!_data) {
|
|
_data = [NSMutableData new];
|
|
}
|
|
@try {
|
|
[_data appendData:data];
|
|
} @catch (NSException *exception) {
|
|
_status = RCTNetworkTaskFinished;
|
|
if (_completionBlock) {
|
|
RCTURLRequestCompletionBlock completionBlock = _completionBlock;
|
|
[self dispatchCallback:^{
|
|
completionBlock(
|
|
self->_response, nil, RCTErrorWithMessage(exception.reason ?: @"Request's received data too long."));
|
|
}];
|
|
}
|
|
[self invalidate];
|
|
return;
|
|
}
|
|
|
|
length = _data.length;
|
|
}
|
|
|
|
int64_t total = _response.expectedContentLength;
|
|
|
|
if (_incrementalDataBlock) {
|
|
RCTURLRequestIncrementalDataBlock incrementalDataBlock = _incrementalDataBlock;
|
|
[self dispatchCallback:^{
|
|
incrementalDataBlock(data, length, total);
|
|
}];
|
|
}
|
|
if (_downloadProgressBlock) {
|
|
RCTURLRequestProgressBlock downloadProgressBlock = _downloadProgressBlock;
|
|
[self dispatchCallback:^{
|
|
downloadProgressBlock(length, total);
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
|
|
{
|
|
if (![self validateRequestToken:requestToken]) {
|
|
return;
|
|
}
|
|
|
|
_status = RCTNetworkTaskFinished;
|
|
if (_completionBlock) {
|
|
RCTURLRequestCompletionBlock completionBlock = _completionBlock;
|
|
NSData *dataCopy = nil;
|
|
{
|
|
std::lock_guard<std::mutex> lock(self->_mutex);
|
|
dataCopy = _data;
|
|
_data = nil;
|
|
}
|
|
[self dispatchCallback:^{
|
|
completionBlock(self->_response, dataCopy, error);
|
|
}];
|
|
}
|
|
[self invalidate];
|
|
}
|
|
|
|
@end
|