mirror of
https://github.com/status-im/react-native.git
synced 2025-01-26 09:19:10 +00:00
Show bundle download progress on iOS
Summary: This shows progress for the download of the JS bundle (different from the packager transform progress that we show already). This is useful especially when loading the JS bundle from a remote source or when developing on device (on simulator + localhost it pretty much just downloads instantly). This will be nice for the expo client since all bundles are loaded over the network and can take several seconds to load. This depends on https://github.com/facebook/metro-bundler/pull/28 to work but won't crash or anything without it, it just won't show the progress percentage. ![img_05070155d2cc-1](https://user-images.githubusercontent.com/2677334/28293828-2c08d974-6b24-11e7-9334-e106ef3326d9.jpeg) **Test plan** Tested that bundle download progress is shown properly in RNTester on both localhost + simulator and on real device with network conditionner to simulate a slow loading bundle. Tested that it doesn't cause issues if the packager doesn't send the Content-Length header. Closes https://github.com/facebook/react-native/pull/15066 Differential Revision: D5449073 Pulled By: shergin fbshipit-source-id: 43a8fb559393bbdc04f77916500e21898695bac5
This commit is contained in:
parent
b58207e61f
commit
ef23d2bdcf
@ -31,12 +31,12 @@
|
|||||||
NSInputStream *inputStream = [NSInputStream inputStreamWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
|
NSInputStream *inputStream = [NSInputStream inputStreamWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
RCTMultipartStreamReader *reader = [[RCTMultipartStreamReader alloc] initWithInputStream:inputStream boundary:@"sample_boundary"];
|
RCTMultipartStreamReader *reader = [[RCTMultipartStreamReader alloc] initWithInputStream:inputStream boundary:@"sample_boundary"];
|
||||||
__block NSInteger count = 0;
|
__block NSInteger count = 0;
|
||||||
BOOL success = [reader readAllParts:^(NSDictionary *headers, NSData *content, BOOL done) {
|
BOOL success = [reader readAllPartsWithCompletionCallback:^(NSDictionary *headers, NSData *content, BOOL done) {
|
||||||
XCTAssertTrue(done);
|
XCTAssertTrue(done);
|
||||||
XCTAssertEqualObjects(headers[@"Content-Type"], @"application/json; charset=utf-8");
|
XCTAssertEqualObjects(headers[@"Content-Type"], @"application/json; charset=utf-8");
|
||||||
XCTAssertEqualObjects([[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding], @"{}");
|
XCTAssertEqualObjects([[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding], @"{}");
|
||||||
count++;
|
count++;
|
||||||
}];
|
} progressCallback: nil];
|
||||||
XCTAssertTrue(success);
|
XCTAssertTrue(success);
|
||||||
XCTAssertEqual(count, 1);
|
XCTAssertEqual(count, 1);
|
||||||
}
|
}
|
||||||
@ -56,13 +56,13 @@
|
|||||||
NSInputStream *inputStream = [NSInputStream inputStreamWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
|
NSInputStream *inputStream = [NSInputStream inputStreamWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
RCTMultipartStreamReader *reader = [[RCTMultipartStreamReader alloc] initWithInputStream:inputStream boundary:@"sample_boundary"];
|
RCTMultipartStreamReader *reader = [[RCTMultipartStreamReader alloc] initWithInputStream:inputStream boundary:@"sample_boundary"];
|
||||||
__block NSInteger count = 0;
|
__block NSInteger count = 0;
|
||||||
BOOL success = [reader readAllParts:^(__unused NSDictionary *headers, NSData *content, BOOL done) {
|
BOOL success = [reader readAllPartsWithCompletionCallback:^(__unused NSDictionary *headers, NSData *content, BOOL done) {
|
||||||
count++;
|
count++;
|
||||||
XCTAssertEqual(done, count == 3);
|
XCTAssertEqual(done, count == 3);
|
||||||
NSString *expectedBody = [NSString stringWithFormat:@"%ld", (long)count];
|
NSString *expectedBody = [NSString stringWithFormat:@"%ld", (long)count];
|
||||||
NSString *actualBody = [[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding];
|
NSString *actualBody = [[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding];
|
||||||
XCTAssertEqualObjects(actualBody, expectedBody);
|
XCTAssertEqualObjects(actualBody, expectedBody);
|
||||||
}];
|
} progressCallback:nil];
|
||||||
XCTAssertTrue(success);
|
XCTAssertTrue(success);
|
||||||
XCTAssertEqual(count, 3);
|
XCTAssertEqual(count, 3);
|
||||||
}
|
}
|
||||||
@ -73,9 +73,9 @@
|
|||||||
NSInputStream *inputStream = [NSInputStream inputStreamWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
|
NSInputStream *inputStream = [NSInputStream inputStreamWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
RCTMultipartStreamReader *reader = [[RCTMultipartStreamReader alloc] initWithInputStream:inputStream boundary:@"sample_boundary"];
|
RCTMultipartStreamReader *reader = [[RCTMultipartStreamReader alloc] initWithInputStream:inputStream boundary:@"sample_boundary"];
|
||||||
__block NSInteger count = 0;
|
__block NSInteger count = 0;
|
||||||
BOOL success = [reader readAllParts:^(__unused NSDictionary *headers, __unused NSData *content, __unused BOOL done) {
|
BOOL success = [reader readAllPartsWithCompletionCallback:^(__unused NSDictionary *headers, __unused NSData *content, __unused BOOL done) {
|
||||||
count++;
|
count++;
|
||||||
}];
|
} progressCallback:nil];
|
||||||
XCTAssertFalse(success);
|
XCTAssertFalse(success);
|
||||||
XCTAssertEqual(count, 0);
|
XCTAssertEqual(count, 0);
|
||||||
}
|
}
|
||||||
@ -93,9 +93,9 @@
|
|||||||
NSInputStream *inputStream = [NSInputStream inputStreamWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
|
NSInputStream *inputStream = [NSInputStream inputStreamWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
RCTMultipartStreamReader *reader = [[RCTMultipartStreamReader alloc] initWithInputStream:inputStream boundary:@"sample_boundary"];
|
RCTMultipartStreamReader *reader = [[RCTMultipartStreamReader alloc] initWithInputStream:inputStream boundary:@"sample_boundary"];
|
||||||
__block NSInteger count = 0;
|
__block NSInteger count = 0;
|
||||||
BOOL success = [reader readAllParts:^(__unused NSDictionary *headers, __unused NSData *content, __unused BOOL done) {
|
BOOL success = [reader readAllPartsWithCompletionCallback:^(__unused NSDictionary *headers, __unused NSData *content, __unused BOOL done) {
|
||||||
count++;
|
count++;
|
||||||
}];
|
} progressCallback:nil];
|
||||||
XCTAssertFalse(success);
|
XCTAssertFalse(success);
|
||||||
XCTAssertEqual(count, 1);
|
XCTAssertEqual(count, 1);
|
||||||
}
|
}
|
||||||
|
@ -198,7 +198,6 @@ static void attemptAsynchronousLoadOfBundleAtURL(NSURL *scriptURL, RCTSourceLoad
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
RCTMultipartDataTask *task = [[RCTMultipartDataTask alloc] initWithURL:scriptURL partHandler:^(NSInteger statusCode, NSDictionary *headers, NSData *data, NSError *error, BOOL done) {
|
RCTMultipartDataTask *task = [[RCTMultipartDataTask alloc] initWithURL:scriptURL partHandler:^(NSInteger statusCode, NSDictionary *headers, NSData *data, NSError *error, BOOL done) {
|
||||||
if (!done) {
|
if (!done) {
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
@ -261,6 +260,11 @@ static void attemptAsynchronousLoadOfBundleAtURL(NSURL *scriptURL, RCTSourceLoad
|
|||||||
}
|
}
|
||||||
|
|
||||||
onComplete(nil, data, data.length);
|
onComplete(nil, data, data.length);
|
||||||
|
} progressHandler:^(NSDictionary *headers, NSNumber *loaded, NSNumber *total) {
|
||||||
|
// Only care about download progress events for the javascript bundle part.
|
||||||
|
if ([headers[@"Content-Type"] isEqualToString:@"application/javascript"]) {
|
||||||
|
onProgress(progressEventFromDownloadProgress(loaded, total));
|
||||||
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[task startTask];
|
[task startTask];
|
||||||
@ -287,6 +291,16 @@ static RCTLoadingProgress *progressEventFromData(NSData *rawData)
|
|||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RCTLoadingProgress *progressEventFromDownloadProgress(NSNumber *total, NSNumber *done)
|
||||||
|
{
|
||||||
|
RCTLoadingProgress *progress = [RCTLoadingProgress new];
|
||||||
|
progress.status = @"Downloading JavaScript bundle";
|
||||||
|
// Progress values are in bytes transform them to kilobytes for smaller numbers.
|
||||||
|
progress.done = done != nil ? @([done integerValue] / 1024) : nil;
|
||||||
|
progress.total = total != nil ? @([total integerValue] / 1024) : nil;
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
static NSDictionary *userInfoForRawResponse(NSString *rawText)
|
static NSDictionary *userInfoForRawResponse(NSString *rawText)
|
||||||
{
|
{
|
||||||
NSDictionary *parsedResponse = RCTJSONParse(rawText, nil);
|
NSDictionary *parsedResponse = RCTJSONParse(rawText, nil);
|
||||||
|
@ -15,7 +15,10 @@ typedef void (^RCTMultipartDataTaskCallback)(NSInteger statusCode, NSDictionary
|
|||||||
|
|
||||||
@interface RCTMultipartDataTask : NSObject
|
@interface RCTMultipartDataTask : NSObject
|
||||||
|
|
||||||
- (instancetype)initWithURL:(NSURL *)url partHandler:(RCTMultipartDataTaskCallback)partHandler;
|
- (instancetype)initWithURL:(NSURL *)url
|
||||||
|
partHandler:(RCTMultipartDataTaskCallback)partHandler
|
||||||
|
progressHandler:(RCTMultipartProgressCallback)progressHandler;
|
||||||
|
|
||||||
- (void)startTask;
|
- (void)startTask;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -30,17 +30,21 @@ static BOOL isStreamTaskSupported() {
|
|||||||
@implementation RCTMultipartDataTask {
|
@implementation RCTMultipartDataTask {
|
||||||
NSURL *_url;
|
NSURL *_url;
|
||||||
RCTMultipartDataTaskCallback _partHandler;
|
RCTMultipartDataTaskCallback _partHandler;
|
||||||
|
RCTMultipartProgressCallback _progressHandler;
|
||||||
NSInteger _statusCode;
|
NSInteger _statusCode;
|
||||||
NSDictionary *_headers;
|
NSDictionary *_headers;
|
||||||
NSString *_boundary;
|
NSString *_boundary;
|
||||||
NSMutableData *_data;
|
NSMutableData *_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithURL:(NSURL *)url partHandler:(RCTMultipartDataTaskCallback)partHandler
|
- (instancetype)initWithURL:(NSURL *)url
|
||||||
|
partHandler:(RCTMultipartDataTaskCallback)partHandler
|
||||||
|
progressHandler:(RCTMultipartProgressCallback)progressHandler
|
||||||
{
|
{
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_url = url;
|
_url = url;
|
||||||
_partHandler = [partHandler copy];
|
_partHandler = [partHandler copy];
|
||||||
|
_progressHandler = [progressHandler copy];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@ -117,9 +121,9 @@ didBecomeInputStream:(NSInputStream *)inputStream
|
|||||||
_partHandler = nil;
|
_partHandler = nil;
|
||||||
NSInteger statusCode = _statusCode;
|
NSInteger statusCode = _statusCode;
|
||||||
|
|
||||||
BOOL completed = [reader readAllParts:^(NSDictionary *headers, NSData *content, BOOL done) {
|
BOOL completed = [reader readAllPartsWithCompletionCallback:^(NSDictionary *headers, NSData *content, BOOL done) {
|
||||||
partHandler(statusCode, headers, content, nil, done);
|
partHandler(statusCode, headers, content, nil, done);
|
||||||
}];
|
} progressCallback:_progressHandler];
|
||||||
if (!completed) {
|
if (!completed) {
|
||||||
partHandler(statusCode, nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:nil], YES);
|
partHandler(statusCode, nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:nil], YES);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
typedef void (^RCTMultipartCallback)(NSDictionary *headers, NSData *content, BOOL done);
|
typedef void (^RCTMultipartCallback)(NSDictionary *headers, NSData *content, BOOL done);
|
||||||
|
typedef void (^RCTMultipartProgressCallback)(NSDictionary *headers, NSNumber *loaded, NSNumber *total);
|
||||||
|
|
||||||
|
|
||||||
// RCTMultipartStreamReader can be used to parse responses with Content-Type: multipart/mixed
|
// RCTMultipartStreamReader can be used to parse responses with Content-Type: multipart/mixed
|
||||||
@ -17,6 +18,7 @@ typedef void (^RCTMultipartCallback)(NSDictionary *headers, NSData *content, BOO
|
|||||||
@interface RCTMultipartStreamReader : NSObject
|
@interface RCTMultipartStreamReader : NSObject
|
||||||
|
|
||||||
- (instancetype)initWithInputStream:(NSInputStream *)stream boundary:(NSString *)boundary;
|
- (instancetype)initWithInputStream:(NSInputStream *)stream boundary:(NSString *)boundary;
|
||||||
- (BOOL)readAllParts:(RCTMultipartCallback)callback;
|
- (BOOL)readAllPartsWithCompletionCallback:(RCTMultipartCallback)callback
|
||||||
|
progressCallback:(RCTMultipartProgressCallback)progressCallback;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -9,11 +9,14 @@
|
|||||||
|
|
||||||
#import "RCTMultipartStreamReader.h"
|
#import "RCTMultipartStreamReader.h"
|
||||||
|
|
||||||
|
#import <QuartzCore/CAAnimation.h>
|
||||||
|
|
||||||
#define CRLF @"\r\n"
|
#define CRLF @"\r\n"
|
||||||
|
|
||||||
@implementation RCTMultipartStreamReader {
|
@implementation RCTMultipartStreamReader {
|
||||||
__strong NSInputStream *_stream;
|
__strong NSInputStream *_stream;
|
||||||
__strong NSString *_boundary;
|
__strong NSString *_boundary;
|
||||||
|
CFTimeInterval _lastDownloadProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithInputStream:(NSInputStream *)stream boundary:(NSString *)boundary
|
- (instancetype)initWithInputStream:(NSInputStream *)stream boundary:(NSString *)boundary
|
||||||
@ -21,6 +24,7 @@
|
|||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_stream = stream;
|
_stream = stream;
|
||||||
_boundary = boundary;
|
_boundary = boundary;
|
||||||
|
_lastDownloadProgress = CACurrentMediaTime();
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@ -42,12 +46,17 @@
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)emitChunk:(NSData *)data callback:(RCTMultipartCallback)callback done:(BOOL)done
|
- (void)emitChunk:(NSData *)data headers:(NSDictionary *)headers callback:(RCTMultipartCallback)callback done:(BOOL)done
|
||||||
{
|
{
|
||||||
NSData *marker = [CRLF CRLF dataUsingEncoding:NSUTF8StringEncoding];
|
NSData *marker = [CRLF CRLF dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
NSRange range = [data rangeOfData:marker options:0 range:NSMakeRange(0, data.length)];
|
NSRange range = [data rangeOfData:marker options:0 range:NSMakeRange(0, data.length)];
|
||||||
if (range.location == NSNotFound) {
|
if (range.location == NSNotFound) {
|
||||||
callback(nil, data, done);
|
callback(nil, data, done);
|
||||||
|
} else if (headers != nil) {
|
||||||
|
// If headers were parsed already just use that to avoid doing it twice.
|
||||||
|
NSInteger bodyStart = range.location + marker.length;
|
||||||
|
NSData *bodyData = [data subdataWithRange:NSMakeRange(bodyStart, data.length - bodyStart)];
|
||||||
|
callback(headers, bodyData, done);
|
||||||
} else {
|
} else {
|
||||||
NSData *headersData = [data subdataWithRange:NSMakeRange(0, range.location)];
|
NSData *headersData = [data subdataWithRange:NSMakeRange(0, range.location)];
|
||||||
NSInteger bodyStart = range.location + marker.length;
|
NSInteger bodyStart = range.location + marker.length;
|
||||||
@ -56,7 +65,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)readAllParts:(RCTMultipartCallback)callback
|
- (void)emitProgress:(NSDictionary *)headers
|
||||||
|
contentLength:(NSUInteger)contentLength
|
||||||
|
final:(BOOL)final
|
||||||
|
callback:(RCTMultipartProgressCallback)callback
|
||||||
|
{
|
||||||
|
if (headers == nil) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Throttle progress events so we don't send more that around 60 per second.
|
||||||
|
CFTimeInterval currentTime = CACurrentMediaTime();
|
||||||
|
|
||||||
|
NSUInteger headersContentLength = headers[@"Content-Length"] != nil ? [headers[@"Content-Length"] unsignedIntValue] : 0;
|
||||||
|
if (callback && (currentTime - _lastDownloadProgress > 0.016 || final)) {
|
||||||
|
_lastDownloadProgress = currentTime;
|
||||||
|
callback(headers, @(headersContentLength), @(contentLength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)readAllPartsWithCompletionCallback:(RCTMultipartCallback)callback
|
||||||
|
progressCallback:(RCTMultipartProgressCallback)progressCallback
|
||||||
{
|
{
|
||||||
NSInteger chunkStart = 0;
|
NSInteger chunkStart = 0;
|
||||||
NSInteger bytesSeen = 0;
|
NSInteger bytesSeen = 0;
|
||||||
@ -64,6 +92,8 @@
|
|||||||
NSData *delimiter = [[NSString stringWithFormat:@"%@--%@%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding];
|
NSData *delimiter = [[NSString stringWithFormat:@"%@--%@%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
NSData *closeDelimiter = [[NSString stringWithFormat:@"%@--%@--%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding];
|
NSData *closeDelimiter = [[NSString stringWithFormat:@"%@--%@--%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
NSMutableData *content = [[NSMutableData alloc] initWithCapacity:1];
|
NSMutableData *content = [[NSMutableData alloc] initWithCapacity:1];
|
||||||
|
NSDictionary *currentHeaders = nil;
|
||||||
|
NSUInteger currentHeadersLength = 0;
|
||||||
|
|
||||||
const NSUInteger bufferLen = 4 * 1024;
|
const NSUInteger bufferLen = 4 * 1024;
|
||||||
uint8_t buffer[bufferLen];
|
uint8_t buffer[bufferLen];
|
||||||
@ -75,6 +105,8 @@
|
|||||||
// to allow for the edge case when the delimiter is cut by read call
|
// to allow for the edge case when the delimiter is cut by read call
|
||||||
NSInteger searchStart = MAX(bytesSeen - (NSInteger)closeDelimiter.length, chunkStart);
|
NSInteger searchStart = MAX(bytesSeen - (NSInteger)closeDelimiter.length, chunkStart);
|
||||||
NSRange remainingBufferRange = NSMakeRange(searchStart, content.length - searchStart);
|
NSRange remainingBufferRange = NSMakeRange(searchStart, content.length - searchStart);
|
||||||
|
|
||||||
|
// Check for delimiters.
|
||||||
NSRange range = [content rangeOfData:delimiter options:0 range:remainingBufferRange];
|
NSRange range = [content rangeOfData:delimiter options:0 range:remainingBufferRange];
|
||||||
if (range.location == NSNotFound) {
|
if (range.location == NSNotFound) {
|
||||||
isCloseDelimiter = YES;
|
isCloseDelimiter = YES;
|
||||||
@ -82,6 +114,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (range.location == NSNotFound) {
|
if (range.location == NSNotFound) {
|
||||||
|
if (currentHeaders == nil) {
|
||||||
|
// Check for the headers delimiter.
|
||||||
|
NSData *headersMarker = [CRLF CRLF dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
NSRange headersRange = [content rangeOfData:headersMarker options:0 range:remainingBufferRange];
|
||||||
|
if (headersRange.location != NSNotFound) {
|
||||||
|
NSData *headersData = [content subdataWithRange:NSMakeRange(chunkStart, headersRange.location - chunkStart)];
|
||||||
|
currentHeadersLength = headersData.length;
|
||||||
|
currentHeaders = [self parseHeaders:headersData];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// When headers are loaded start sending progress callbacks.
|
||||||
|
[self emitProgress:currentHeaders
|
||||||
|
contentLength:content.length - currentHeadersLength
|
||||||
|
final:NO
|
||||||
|
callback:progressCallback];
|
||||||
|
}
|
||||||
|
|
||||||
bytesSeen = content.length;
|
bytesSeen = content.length;
|
||||||
NSInteger bytesRead = [_stream read:buffer maxLength:bufferLen];
|
NSInteger bytesRead = [_stream read:buffer maxLength:bufferLen];
|
||||||
if (bytesRead <= 0 || _stream.streamError) {
|
if (bytesRead <= 0 || _stream.streamError) {
|
||||||
@ -98,7 +147,13 @@
|
|||||||
// Ignore preamble
|
// Ignore preamble
|
||||||
if (chunkStart > 0) {
|
if (chunkStart > 0) {
|
||||||
NSData *chunk = [content subdataWithRange:NSMakeRange(chunkStart, length)];
|
NSData *chunk = [content subdataWithRange:NSMakeRange(chunkStart, length)];
|
||||||
[self emitChunk:chunk callback:callback done:isCloseDelimiter];
|
[self emitProgress:currentHeaders
|
||||||
|
contentLength:chunk.length - currentHeadersLength
|
||||||
|
final:YES
|
||||||
|
callback:progressCallback];
|
||||||
|
[self emitChunk:chunk headers:currentHeaders callback:callback done:isCloseDelimiter];
|
||||||
|
currentHeaders = nil;
|
||||||
|
currentHeadersLength = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCloseDelimiter) {
|
if (isCloseDelimiter) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user