diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 1cbc283..bc45b4f 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -1067,11 +1067,22 @@ static const NSUInteger AFMultipartBodyStreamProviderDefaultBufferLength = 4096; #pragma mark - NSStreamDelegate -- (void)stream:(NSStream __unused *)stream - handleEvent:(NSStreamEvent)eventCode -{ +/** + This retry works around a nasty problem in which mutli-part uploads will fail due to the stream delegate being sent a `NSStreamEventHasSpaceAvailable` event before the input stream has finished opening. This workaround simply replays the event after allowing the run-loop to cycle, providing enough time for the input stream to finish opening. It appears that this bug is in the CFNetwork layer. (See https://github.com/AFNetworking/AFNetworking/issues/948) + */ +- (void)retryWrite:(NSStream *)stream { + [self stream:stream handleEvent:NSStreamEventHasSpaceAvailable]; +} + +- (void)stream:(NSStream *)stream + handleEvent:(NSStreamEvent)eventCode { if (eventCode & NSStreamEventHasSpaceAvailable) { - [self handleOutputStreamSpaceAvailable]; + if (self.inputStream.streamStatus < NSStreamStatusOpen) { + // See comments in `retryWrite:` for details + [self performSelector:@selector(retryWrite:) withObject:stream afterDelay:0.1]; + } else { + [self handleOutputStreamSpaceAvailable]; + } } } diff --git a/Tests/AFHTTPClientTests.m b/Tests/AFHTTPClientTests.m index 0ba0032..31e65e4 100644 --- a/Tests/AFHTTPClientTests.m +++ b/Tests/AFHTTPClientTests.m @@ -199,4 +199,16 @@ expect(responseDictionary[@"form"]).to.equal(@{ @"key": @"value" }); } +- (void)testMultipartUploadDoesNotFailDueToStreamSentAnEventBeforeBeingOpenedError { + NSString *pathToImage = [[NSBundle bundleForClass:[AFHTTPClient class]] pathForResource:@"abide" ofType:@"jpg"]; + NSData *imageData = [NSData dataWithContentsOfFile:pathToImage]; + NSMutableURLRequest *request = [self.client multipartFormRequestWithMethod:@"POST" path:@"/post" parameters:@{ @"this": @"that" } constructingBodyWithBlock:^(id formData) { + [formData appendPartWithFileData:imageData name:@"item[photos_attributes][][photo]" fileName:@"item-image.png" mimeType:@"image/jpg"]; + }]; + AFHTTPRequestOperation *operation = [self.client HTTPRequestOperationWithRequest:request success:nil failure:nil]; + [self.client enqueueHTTPRequestOperation:operation]; + expect(operation.isFinished).will.beTruthy(); + expect(operation.error).notTo.equal(NSURLErrorTimedOut); +} + @end diff --git a/Tests/abide.jpg b/Tests/abide.jpg new file mode 100644 index 0000000..6358ad3 Binary files /dev/null and b/Tests/abide.jpg differ