diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index 8d9bd00..93abec6 100755 --- a/AFNetworking/AFHTTPClient.h +++ b/AFNetworking/AFHTTPClient.h @@ -501,6 +501,9 @@ extern NSString * const AFNetworkingReachabilityNotificationStatusItem; #pragma mark - +extern NSUInteger const kAFUploadStream3GSuggestedPacketSize; +extern NSUInteger const kAFUploadStream3GSuggestedDelay; + /** The `AFMultipartFormData` protocol defines the methods supported by the parameter in the block argument of `AFHTTPClient -multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`. */ @@ -544,4 +547,15 @@ extern NSString * const AFNetworkingReachabilityNotificationStatusItem; - (void)appendPartWithFormData:(NSData *)data name:(NSString *)name; +/** + Throttles request bandwidth by limiting the packet size and adding a delay for each chunk read from the upload stream. + + @param numberOfBytes Maximum packet size, in number of bytes. The default packet size for an input stream is 32kb. + @param delay Duration of delay each time a packet is read. By default, no delay is set. + + @discussion When uploading over a 3G or EDGE connection, requests may fail with "request body stream exhausted". Setting a maximum packet size and delay according to the recommended values (`kAFUploadStream3GSuggestedPacketSize` and `kAFUploadStream3GSuggestedDelay`) lowers the risk of the input stream exceeding its allocated bandwidth. Unfortunately, as of iOS 6, there is no definite way to distinguish between a 3G, EDGE, or LTE connection. As such, it is not recommended that you throttle bandwidth based solely on network reachability. Instead, you should consider checking for the "request body stream exhausted" in a failure block, and then retrying the request with throttled bandwidth. + */ +- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes + delay:(NSTimeInterval)delay; + @end diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 972911b..9383cc7 100755 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -708,6 +708,9 @@ static inline NSString * AFContentTypeForPathExtension(NSString *extension) { #endif } +NSUInteger const kAFUploadStream3GSuggestedPacketSize = 1024 * 16; +NSUInteger const kAFUploadStream3GSuggestedDelay = 0.2; + @interface AFHTTPBodyPart : NSObject @property (nonatomic, assign) NSStringEncoding stringEncoding; @@ -727,6 +730,8 @@ static inline NSString * AFContentTypeForPathExtension(NSString *extension) { @interface AFMultipartBodyStream : NSInputStream +@property (nonatomic, assign) NSUInteger numberOfBytesInPacket; +@property (nonatomic, assign) NSTimeInterval delay; @property (readonly) unsigned long long contentLength; @property (readonly, getter = isEmpty) BOOL empty; @@ -834,6 +839,13 @@ static inline NSString * AFContentTypeForPathExtension(NSString *extension) { [self.bodyStream appendHTTPBodyPart:bodyPart]; } +- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes + delay:(NSTimeInterval)delay +{ + self.bodyStream.numberOfBytesInPacket = numberOfBytes; + self.bodyStream.delay = delay; +} + - (NSMutableURLRequest *)requestByFinalizingMultipartFormData { if ([self.bodyStream isEmpty]) { return self.request; @@ -861,6 +873,8 @@ static inline NSString * AFContentTypeForPathExtension(NSString *extension) { @property (nonatomic, retain) NSMutableArray *HTTPBodyParts; @property (nonatomic, retain) NSEnumerator *HTTPBodyPartEnumerator; @property (nonatomic, retain) AFHTTPBodyPart *currentHTTPBodyPart; +@property (nonatomic, retain) NSDate *lastReadAt; +@property (nonatomic, assign) NSInteger lastBytesRead; @end @implementation AFMultipartBodyStream @@ -870,6 +884,8 @@ static inline NSString * AFContentTypeForPathExtension(NSString *extension) { @synthesize HTTPBodyParts = _HTTPBodyParts; @synthesize HTTPBodyPartEnumerator = _HTTPBodyPartEnumerator; @synthesize currentHTTPBodyPart = _currentHTTPBodyPart; +@synthesize numberOfBytesInPacket = _numberOfBytesInPacket; +@synthesize delay = _delay; - (id)initWithStringEncoding:(NSStringEncoding)encoding { self = [super init]; @@ -879,6 +895,7 @@ static inline NSString * AFContentTypeForPathExtension(NSString *extension) { self.stringEncoding = encoding; self.HTTPBodyParts = [NSMutableArray array]; + self.numberOfBytesInPacket = NSIntegerMax; return self; } @@ -912,16 +929,19 @@ static inline NSString * AFContentTypeForPathExtension(NSString *extension) { NSInteger bytesRead = 0; - while ((NSUInteger)bytesRead < length) { + while ((NSUInteger)bytesRead < MIN(length, self.numberOfBytesInPacket)) { if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) { if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) { break; } } else { bytesRead += [self.currentHTTPBodyPart read:&buffer[bytesRead] maxLength:length - bytesRead]; + if (self.delay > 0.0f) { + [NSThread sleepForTimeInterval:self.delay]; + } } } - + return bytesRead; }