Fix AFHTTPBodyPart's read:maxLength: method to no longer treat a negative response from -[NSInputStream hasBytesAvailable] as an indication that the stream has come to an end, and instead examine the stream's state.
This commit is contained in:
parent
d492c4bff5
commit
1136c8bece
5 changed files with 133 additions and 4 deletions
|
|
@ -1290,11 +1290,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];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
Tests/AFBufferedInputStreamProvider.h
Normal file
17
Tests/AFBufferedInputStreamProvider.h
Normal file
|
|
@ -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 <Foundation/Foundation.h>
|
||||
|
||||
@interface AFBufferedInputStreamProvider : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSUInteger bytesWritten;
|
||||
|
||||
- (id) initWithData:(NSData *)data inputStream:(NSInputStream *__autoreleasing *)outInputStream;
|
||||
|
||||
@end
|
||||
85
Tests/AFBufferedInputStreamProvider.m
Normal file
85
Tests/AFBufferedInputStreamProvider.m
Normal file
|
|
@ -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 () <NSStreamDelegate>
|
||||
@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
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
// THE SOFTWARE.
|
||||
|
||||
#import "AFNetworkingTests.h"
|
||||
#import "AFBufferedInputStreamProvider.h"
|
||||
|
||||
@interface AFHTTPClientTests : SenTestCase
|
||||
@property (readwrite, nonatomic, strong) AFHTTPClient *client;
|
||||
|
|
@ -334,4 +335,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<AFMultipartFormData> 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
|
||||
|
|
|
|||
|
|
@ -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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
A13DC4CE1783470C00F146CE /* AFBufferedInputStreamProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFBufferedInputStreamProvider.m; sourceTree = "<group>"; };
|
||||
A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFURLConnectionOperationTests.m; sourceTree = "<group>"; };
|
||||
F8C6F281174D2C6200B154D5 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Icon.png; path = ../Example/Icon.png; sourceTree = "<group>"; };
|
||||
F8D62D39175ABF5E00C717C3 /* AFMockURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFMockURLProtocol.h; sourceTree = "<group>"; };
|
||||
|
|
@ -171,6 +174,8 @@
|
|||
29A9CE2017456336002360C8 /* AFJSONRequestOperationTests.m */,
|
||||
667B268017599C5800764906 /* AFImageRequestOperationTests.m */,
|
||||
2580153A173EB3A70026AA6E /* AFHTTPClientTests.m */,
|
||||
A13DC4CD1783470C00F146CE /* AFBufferedInputStreamProvider.h */,
|
||||
A13DC4CE1783470C00F146CE /* AFBufferedInputStreamProvider.m */,
|
||||
);
|
||||
name = Tests;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue