Merge pull request #368 from AFNetworking/experimental-multipart-streaming
Multipart Upload Streaming
This commit is contained in:
commit
2071196450
2 changed files with 87 additions and 38 deletions
|
|
@ -43,17 +43,18 @@
|
||||||
|
|
||||||
NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change";
|
NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change";
|
||||||
|
|
||||||
static NSString * const kAFMultipartFormBoundary = @"Boundary+0xAbCdEfGbOuNdArY";
|
|
||||||
|
|
||||||
@interface AFMultipartFormData : NSObject <AFMultipartFormData> {
|
@interface AFMultipartFormData : NSObject <AFMultipartFormData> {
|
||||||
@private
|
@private
|
||||||
|
NSMutableURLRequest *_request;
|
||||||
NSStringEncoding _stringEncoding;
|
NSStringEncoding _stringEncoding;
|
||||||
NSMutableData *_mutableData;
|
NSOutputStream *_outputStream;
|
||||||
|
NSString *_temporaryFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property (readonly) NSData *data;
|
- (id)initWithURLRequest:(NSMutableURLRequest *)request
|
||||||
|
stringEncoding:(NSStringEncoding)encoding;
|
||||||
|
|
||||||
- (id)initWithStringEncoding:(NSStringEncoding)encoding;
|
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
@ -491,7 +492,7 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
|
||||||
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData>formData))block
|
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData>formData))block
|
||||||
{
|
{
|
||||||
NSMutableURLRequest *request = [self requestWithMethod:method path:path parameters:nil];
|
NSMutableURLRequest *request = [self requestWithMethod:method path:path parameters:nil];
|
||||||
__block AFMultipartFormData *formData = [[AFMultipartFormData alloc] initWithStringEncoding:self.stringEncoding];
|
__block AFMultipartFormData *formData = [[[AFMultipartFormData alloc] initWithURLRequest:request stringEncoding:self.stringEncoding] autorelease];
|
||||||
|
|
||||||
for (AFQueryStringComponent *component in AFQueryStringComponentsFromKeyAndValue(nil, parameters)) {
|
for (AFQueryStringComponent *component in AFQueryStringComponentsFromKeyAndValue(nil, parameters)) {
|
||||||
NSData *data = nil;
|
NSData *data = nil;
|
||||||
|
|
@ -510,12 +511,7 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
|
||||||
block(formData);
|
block(formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kAFMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"];
|
return [formData requestByFinalizingMultipartFormData];
|
||||||
[request setHTTPBody:[formData data]];
|
|
||||||
|
|
||||||
[formData autorelease];
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
|
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
|
||||||
|
|
@ -670,6 +666,25 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
|
||||||
|
static NSString * const kAFMultipartTemporaryFileDirectoryName = @"com.alamofire.uploads";
|
||||||
|
|
||||||
|
static NSString * AFMultipartTemporaryFileDirectoryPath() {
|
||||||
|
static NSString *multipartTemporaryFilePath = nil;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
multipartTemporaryFilePath = [[NSTemporaryDirectory() stringByAppendingPathComponent:kAFMultipartTemporaryFileDirectoryName] copy];
|
||||||
|
|
||||||
|
NSError *error = nil;
|
||||||
|
if(![[NSFileManager defaultManager] createDirectoryAtPath:multipartTemporaryFilePath withIntermediateDirectories:YES attributes:nil error:&error]) {
|
||||||
|
NSLog(@"Failed to create multipary temporary file directory at %@", multipartTemporaryFilePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return multipartTemporaryFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSString * const kAFMultipartFormBoundary = @"Boundary+0xAbCdEfGbOuNdArY";
|
||||||
|
|
||||||
static NSString * const kAFMultipartFormCRLF = @"\r\n";
|
static NSString * const kAFMultipartFormCRLF = @"\r\n";
|
||||||
|
|
||||||
static inline NSString * AFMultipartFormInitialBoundary() {
|
static inline NSString * AFMultipartFormInitialBoundary() {
|
||||||
|
|
@ -685,45 +700,76 @@ static inline NSString * AFMultipartFormFinalBoundary() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface AFMultipartFormData ()
|
@interface AFMultipartFormData ()
|
||||||
|
@property (readwrite, nonatomic, retain) NSMutableURLRequest *request;
|
||||||
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
|
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
|
||||||
@property (readwrite, nonatomic, retain) NSMutableData *mutableData;
|
@property (readwrite, nonatomic, retain) NSOutputStream *outputStream;
|
||||||
|
@property (readwrite, nonatomic, copy) NSString *temporaryFilePath;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation AFMultipartFormData
|
@implementation AFMultipartFormData
|
||||||
|
@synthesize request = _request;
|
||||||
@synthesize stringEncoding = _stringEncoding;
|
@synthesize stringEncoding = _stringEncoding;
|
||||||
@synthesize mutableData = _mutableData;
|
@synthesize outputStream = _outputStream;
|
||||||
|
@synthesize temporaryFilePath = _temporaryFilePath;
|
||||||
|
|
||||||
- (id)initWithStringEncoding:(NSStringEncoding)encoding {
|
- (id)initWithURLRequest:(NSMutableURLRequest *)request
|
||||||
|
stringEncoding:(NSStringEncoding)encoding
|
||||||
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (!self) {
|
if (!self) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.request = request;
|
||||||
self.stringEncoding = encoding;
|
self.stringEncoding = encoding;
|
||||||
self.mutableData = [NSMutableData dataWithLength:0];
|
|
||||||
|
self.temporaryFilePath = [AFMultipartTemporaryFileDirectoryPath() stringByAppendingPathComponent:[NSString stringWithFormat:@"%u", [[self.request URL] hash]]];
|
||||||
|
self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.temporaryFilePath append:NO];
|
||||||
|
|
||||||
|
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
|
||||||
|
[self.outputStream scheduleInRunLoop:runLoop forMode:NSRunLoopCommonModes];
|
||||||
|
[self.outputStream open];
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
[_mutableData release];
|
[_request release];
|
||||||
|
|
||||||
|
if (_outputStream) {
|
||||||
|
[_outputStream close];
|
||||||
|
[_outputStream release];
|
||||||
|
_outputStream = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
[_temporaryFilePath release];
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSData *)data {
|
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
|
||||||
NSMutableData *finalizedData = [NSMutableData dataWithData:self.mutableData];
|
[self appendData:[AFMultipartFormFinalBoundary() dataUsingEncoding:self.stringEncoding]];
|
||||||
[finalizedData appendData:[AFMultipartFormFinalBoundary() dataUsingEncoding:self.stringEncoding]];
|
|
||||||
return finalizedData;
|
[self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kAFMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"];
|
||||||
|
[self.request setValue:[[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] stringValue] forHTTPHeaderField:@"Content-Length"];
|
||||||
|
[self.request setHTTPBodyStream:[NSInputStream inputStreamWithFileAtPath:self.temporaryFilePath]];
|
||||||
|
|
||||||
|
[self.outputStream close];
|
||||||
|
|
||||||
|
return self.request;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - AFMultipartFormData
|
#pragma mark - AFMultipartFormData
|
||||||
|
|
||||||
- (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body {
|
- (void)appendBoundary {
|
||||||
if ([self.mutableData length] == 0) {
|
if ([[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] integerValue] == 0) {
|
||||||
[self appendString:AFMultipartFormInitialBoundary()];
|
[self appendString:AFMultipartFormInitialBoundary()];
|
||||||
} else {
|
} else {
|
||||||
[self appendString:AFMultipartFormEncapsulationBoundary()];
|
[self appendString:AFMultipartFormEncapsulationBoundary()];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body {
|
||||||
|
[self appendBoundary];
|
||||||
|
|
||||||
for (NSString *field in [headers allKeys]) {
|
for (NSString *field in [headers allKeys]) {
|
||||||
[self appendString:[NSString stringWithFormat:@"%@: %@%@", field, [headers valueForKey:field], kAFMultipartFormCRLF]];
|
[self appendString:[NSString stringWithFormat:@"%@: %@%@", field, [headers valueForKey:field], kAFMultipartFormCRLF]];
|
||||||
|
|
@ -754,7 +800,7 @@ static inline NSString * AFMultipartFormFinalBoundary() {
|
||||||
[userInfo setValue:fileURL forKey:NSURLErrorFailingURLErrorKey];
|
[userInfo setValue:fileURL forKey:NSURLErrorFailingURLErrorKey];
|
||||||
[userInfo setValue:NSLocalizedString(@"Expected URL to be a file URL", nil) forKey:NSLocalizedFailureReasonErrorKey];
|
[userInfo setValue:NSLocalizedString(@"Expected URL to be a file URL", nil) forKey:NSLocalizedFailureReasonErrorKey];
|
||||||
if (error != NULL) {
|
if (error != NULL) {
|
||||||
*error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadURL userInfo:userInfo] autorelease];
|
*error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadURL userInfo:userInfo] autorelease];
|
||||||
}
|
}
|
||||||
|
|
||||||
return NO;
|
return NO;
|
||||||
|
|
@ -775,12 +821,15 @@ static inline NSString * AFMultipartFormFinalBoundary() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)appendData:(NSData *)data {
|
|
||||||
[self.mutableData appendData:data];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)appendString:(NSString *)string {
|
- (void)appendString:(NSString *)string {
|
||||||
[self appendData:[string dataUsingEncoding:self.stringEncoding]];
|
[self appendData:[string dataUsingEncoding:self.stringEncoding]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)appendData:(NSData *)data {
|
||||||
|
if ([self.outputStream hasSpaceAvailable]) {
|
||||||
|
const uint8_t *dataBuffer = (uint8_t *) [data bytes];
|
||||||
|
[self.outputStream write:&dataBuffer[0] maxLength:[data length]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,7 @@
|
||||||
F8E469571395739C00DB05C8 /* Project object */ = {
|
F8E469571395739C00DB05C8 /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastUpgradeCheck = 0420;
|
LastUpgradeCheck = 0430;
|
||||||
ORGANIZATIONNAME = Gowalla;
|
ORGANIZATIONNAME = Gowalla;
|
||||||
};
|
};
|
||||||
buildConfigurationList = F8E4695A1395739C00DB05C8 /* Build configuration list for PBXProject "AFNetworking iOS Example" */;
|
buildConfigurationList = F8E4695A1395739C00DB05C8 /* Build configuration list for PBXProject "AFNetworking iOS Example" */;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue