diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 303ff89..3fe034d 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -1295,11 +1295,9 @@ typedef enum { } if (_phase == AFBodyPhase) { - if ([self.inputStream hasBytesAvailable]) { - bytesRead += [self.inputStream read:&buffer[bytesRead] maxLength:(length - (NSUInteger)bytesRead)]; - } + bytesRead += [self.inputStream read:&buffer[bytesRead] maxLength:(length - (NSUInteger)bytesRead)]; - if (![self.inputStream hasBytesAvailable]) { + if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) { [self transitionToNextPhase]; } } diff --git a/Tests/AFBufferedInputStreamProvider.h b/Tests/AFBufferedInputStreamProvider.h new file mode 100644 index 0000000..ac66328 --- /dev/null +++ b/Tests/AFBufferedInputStreamProvider.h @@ -0,0 +1,17 @@ +// +// AFDelayingInputStreamProvider.h +// AFNetworking Tests +// +// Created by Dev Floater 53 on 2013-07-02. +// Copyright (c) 2013 AFNetworking. All rights reserved. +// + +#import + +@interface AFBufferedInputStreamProvider : NSObject + +@property (nonatomic, readonly) NSUInteger bytesWritten; + +- (id) initWithData:(NSData *)data inputStream:(NSInputStream *__autoreleasing *)outInputStream; + +@end diff --git a/Tests/AFBufferedInputStreamProvider.m b/Tests/AFBufferedInputStreamProvider.m new file mode 100644 index 0000000..3dccee9 --- /dev/null +++ b/Tests/AFBufferedInputStreamProvider.m @@ -0,0 +1,85 @@ +// +// AFDelayingInputStreamProvider.m +// AFNetworking Tests +// +// Created by Dev Floater 53 on 2013-07-02. +// Copyright (c) 2013 AFNetworking. All rights reserved. +// + +#import "AFBufferedInputStreamProvider.h" + +@interface AFBufferedInputStreamProvider () +@property (nonatomic, strong) NSData *sourceData; + +@property (nonatomic, strong) NSInputStream *inputStream; +@property (nonatomic, strong) NSOutputStream *outputStream; +@end + +@implementation AFBufferedInputStreamProvider + +- (id) initWithData:(NSData *)data inputStream:(NSInputStream *__autoreleasing *)outInputStream { + NSParameterAssert(outInputStream); + self = [super init]; + if (!self) { + return nil; + } + + self.sourceData = data; + + CFReadStreamRef readStream; + CFWriteStreamRef writeStream; + CFStreamCreateBoundPair(NULL, &readStream, &writeStream, 16); + self.inputStream = CFBridgingRelease(readStream); + self.outputStream = CFBridgingRelease(writeStream); + self.outputStream.delegate = self; + [self.outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + [self.outputStream open]; + *outInputStream = self.inputStream; + + return self; +} + +- (void)dealloc { + [self cleanup]; +} + +- (void) cleanup { + [self.outputStream close]; + self.outputStream.delegate = nil; + [self.outputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + self.outputStream = nil; + + [self.inputStream close]; + self.inputStream = nil; +} + +- (void) writeBytesIfPossible { + while ([self.outputStream hasSpaceAvailable] && self.bytesWritten < [self.sourceData length]) { + const uint8_t *bytes = [self.sourceData bytes]; + NSInteger res = [self.outputStream write:bytes+self.bytesWritten maxLength:[self.sourceData length]-self.bytesWritten]; + + if (res < 0) { + [self cleanup]; + return; + } + else { + _bytesWritten += res; + } + } + + if (self.bytesWritten >= [self.sourceData length]) { + [self cleanup]; + } +} + + +- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { + if (aStream == self.outputStream && (eventCode & NSStreamEventHasSpaceAvailable)) { + [self writeBytesIfPossible]; + } + else if (eventCode & NSStreamEventErrorOccurred || eventCode & NSStreamEventEndEncountered) { + [self cleanup]; + } +} + +@end diff --git a/Tests/AFHTTPClientTests.m b/Tests/AFHTTPClientTests.m index d7188b2..4ab1e00 100644 --- a/Tests/AFHTTPClientTests.m +++ b/Tests/AFHTTPClientTests.m @@ -21,6 +21,7 @@ // THE SOFTWARE. #import "AFNetworkingTests.h" +#import "AFBufferedInputStreamProvider.h" @interface AFHTTPClientTests : SenTestCase @property (readwrite, nonatomic, strong) AFHTTPClient *client; @@ -349,4 +350,26 @@ expect(operation.error).notTo.equal(NSURLErrorTimedOut); } +- (void)testMultipartUploadDoesNotPrematurelyCloseInputStream { + NSData *data = [@"Here is some data. Its length is larger than that of the buffer size of the bound stream pair." dataUsingEncoding:NSUTF8StringEncoding]; + NSInputStream *inputStream; + __block AFBufferedInputStreamProvider *streamProvider = [[AFBufferedInputStreamProvider alloc] initWithData:data inputStream:&inputStream]; + __block NSUInteger bytesWritten = 0; + + NSMutableURLRequest *request = [self.client multipartFormRequestWithMethod:@"POST" path:@"/post" parameters:@{ @"foo": @"bar" } constructingBodyWithBlock:^(id formData) { + [formData appendPartWithInputStream:inputStream name:@"data" fileName:@"string.txt" length:[data length] mimeType:@"text/plain"]; + }]; + AFHTTPRequestOperation *operation = [self.client HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { + bytesWritten = streamProvider.bytesWritten; + streamProvider = nil; + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + bytesWritten = streamProvider.bytesWritten; + streamProvider = nil; + }]; + + [self.client enqueueHTTPRequestOperation:operation]; + expect(operation.isFinished).will.beTruthy(); + expect(bytesWritten).will.equal([data length]); +} + @end diff --git a/Tests/AFNetworking Tests.xcodeproj/project.pbxproj b/Tests/AFNetworking Tests.xcodeproj/project.pbxproj index 6778f72..1294d83 100644 --- a/Tests/AFNetworking Tests.xcodeproj/project.pbxproj +++ b/Tests/AFNetworking Tests.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 29A9CE2117456336002360C8 /* AFJSONRequestOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A9CE2017456336002360C8 /* AFJSONRequestOperationTests.m */; }; 29A9CE2217456336002360C8 /* AFJSONRequestOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A9CE2017456336002360C8 /* AFJSONRequestOperationTests.m */; }; 667B268117599C5800764906 /* AFImageRequestOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 667B268017599C5800764906 /* AFImageRequestOperationTests.m */; }; + A13DC4CF1783470C00F146CE /* AFBufferedInputStreamProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = A13DC4CE1783470C00F146CE /* AFBufferedInputStreamProvider.m */; }; A7DC62A917592E4800EBEC2F /* AFURLConnectionOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */; }; A7DC62AA17592E4800EBEC2F /* AFURLConnectionOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */; }; AC11A74923B64A3096ACADFC /* libPods-osx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 96A923755B00464187DEDBAF /* libPods-osx.a */; }; @@ -69,6 +70,8 @@ 55E73C267F33406A9F92476C /* libPods-ios.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ios.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 667B268017599C5800764906 /* AFImageRequestOperationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFImageRequestOperationTests.m; sourceTree = ""; }; 96A923755B00464187DEDBAF /* libPods-osx.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-osx.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A13DC4CD1783470C00F146CE /* AFBufferedInputStreamProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFBufferedInputStreamProvider.h; sourceTree = ""; }; + A13DC4CE1783470C00F146CE /* AFBufferedInputStreamProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFBufferedInputStreamProvider.m; sourceTree = ""; }; A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFURLConnectionOperationTests.m; sourceTree = ""; }; F8C6F281174D2C6200B154D5 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Icon.png; path = ../Example/Icon.png; sourceTree = ""; }; F8D62D39175ABF5E00C717C3 /* AFMockURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFMockURLProtocol.h; sourceTree = ""; }; @@ -171,6 +174,8 @@ 29A9CE2017456336002360C8 /* AFJSONRequestOperationTests.m */, 667B268017599C5800764906 /* AFImageRequestOperationTests.m */, 2580153A173EB3A70026AA6E /* AFHTTPClientTests.m */, + A13DC4CD1783470C00F146CE /* AFBufferedInputStreamProvider.h */, + A13DC4CE1783470C00F146CE /* AFBufferedInputStreamProvider.m */, ); name = Tests; sourceTree = ""; @@ -329,6 +334,7 @@ A7DC62A917592E4800EBEC2F /* AFURLConnectionOperationTests.m in Sources */, 667B268117599C5800764906 /* AFImageRequestOperationTests.m in Sources */, F8D62D3B175ABF5E00C717C3 /* AFMockURLProtocol.m in Sources */, + A13DC4CF1783470C00F146CE /* AFBufferedInputStreamProvider.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };