Ben Copsey

Start work on test for file-streamed request body

Improve comments
... ... @@ -3,7 +3,7 @@
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
//
#import "ASIHTTPRequest.h"
... ...
... ... @@ -3,7 +3,7 @@
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
//
#import "ASIFormDataRequest.h"
... ...
... ... @@ -2,7 +2,7 @@
// ASIHTTPRequest.h
//
// Created by Ben Copsey on 04/10/2007.
// Copyright 2007-2008 All-Seeing Interactive. All rights reserved.
// Copyright 2007-2009 All-Seeing Interactive. All rights reserved.
//
// A guide to the main features is available at:
// http://allseeing-i.com/ASIHTTPRequest
... ... @@ -40,9 +40,26 @@ typedef enum _ASINetworkErrorType {
// HTTP method to use (GET / POST / PUT / DELETE). Defaults to GET
NSString *requestMethod;
// Request body
// Request body - only used when the whole body is stored in memory (shouldStreamPostDataFromDisk is false)
NSMutableData *postBody;
// When true, post body will be streamed from a file on disk, rather than loaded into memory at once (useful for large uploads)
// Automatically set to true in ASIFormDataRequests when using setFile:forKey:
BOOL shouldStreamPostDataFromDisk;
// Path to file used to store post body (when shouldStreamPostDataFromDisk is true)
// You can set this yourself - useful if you want to PUT a file from local disk
NSString *postBodyFilePath;
// Set to true when ASIHTTPRequest automatically created a temporary file containing the request body (when true, the file at postBodyFilePath will be deleted at the end of the request)
BOOL didCreateTemporaryPostDataFile;
// Used when writing to the post body when shouldStreamPostDataFromDisk is true (via appendPostData: or appendPostDataFromFile:)
NSOutputStream *postBodyWriteStream;
// Used for reading from the post body when sending the request
NSInputStream *postBodyReadStream;
// Dictionary for custom HTTP request headers
NSMutableDictionary *requestHeaders;
... ... @@ -192,10 +209,7 @@ typedef enum _ASINetworkErrorType {
// Custom user information assosiated with the request
NSDictionary *userInfo;
NSString *postBodyFilePath;
NSOutputStream *postBodyWriteStream;
NSInputStream *postBodyReadStream;
BOOL shouldStreamPostDataFromDisk;
}
#pragma mark init / dealloc
... ... @@ -210,6 +224,7 @@ typedef enum _ASINetworkErrorType {
- (void)buildPostBody;
// Called to add data to the post body. Will append to postBody when shouldStreamPostDataFromDisk is false, or write to postBodyWriteStream when true
- (void)appendPostData:(NSData *)data;
- (void)appendPostDataFromFile:(NSString *)file;
... ... @@ -239,6 +254,10 @@ typedef enum _ASINetworkErrorType {
// No need to call this if the request succeeds - it is removed automatically
- (void)removeTemporaryDownloadFile;
// Call to remove the file used as the request body
// No need to call this if the request succeeds and you didn't specify postBodyFilePath manually - it is removed automatically
- (void)removePostDataFile;
#pragma mark upload/download progress
// Called on main thread to update progress delegates
... ... @@ -371,4 +390,5 @@ typedef enum _ASINetworkErrorType {
@property (retain) NSOutputStream *postBodyWriteStream;
@property (retain) NSInputStream *postBodyReadStream;
@property (assign) BOOL shouldStreamPostDataFromDisk;
@property (assign) BOOL didCreateTemporaryPostDataFile;
@end
... ...
... ... @@ -56,6 +56,7 @@ static NSError *ASIUnableToCreateRequestError;
[super initialize];
}
- (id)initWithURL:(NSURL *)newURL
{
self = [super init];
... ... @@ -76,6 +77,11 @@ static NSError *ASIUnableToCreateRequestError;
requestAuthentication = NULL;
haveBuiltPostBody = NO;
request = NULL;
[self setDidCreateTemporaryPostDataFile:NO];
[self setPostBodyFilePath:nil];
[self setPostBodyWriteStream:nil];
[self setPostBodyReadStream:nil];
[self setShouldStreamPostDataFromDisk:NO];
[self setAllowCompressedResponse:YES];
[self setDefaultResponseEncoding:NSISOLatin1StringEncoding];
[self setUploadBufferSize:0];
... ... @@ -130,6 +136,9 @@ static NSError *ASIUnableToCreateRequestError;
[requestMethod release];
[cancelledLock release];
[authenticationMethod release];
[postBodyFilePath release];
[postBodyWriteStream release];
[postBodyReadStream release];
[super dealloc];
}
... ... @@ -145,34 +154,38 @@ static NSError *ASIUnableToCreateRequestError;
}
// Subclasses should override this method if they need to create POST content for this request
// This function will be called either just before a request starts, or when postLength is needed, whichever comes first
// postLength must be set by the time this function is complete - calling setPostBody: will do this for you
// postLength must be set by the time this function is complete
- (void)buildPostBody
{
if ([self postBodyFilePath] && [self postBodyWriteStream]) {
// Are we submitting the request body from a file on disk
if ([self postBodyFilePath]) {
// If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write tream
if ([self postBodyWriteStream]) {
[[self postBodyWriteStream] close];
[self setPostBodyWriteStream:nil];
[self setPostLength:[[[NSFileManager defaultManager] fileAttributesAtPath:[self postBodyFilePath] traverseLink:NO] fileSize]];
[self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",postLength]];
if (postBody && postLength > 0 && ![requestMethod isEqualToString:@"POST"] && ![requestMethod isEqualToString:@"PUT"]) {
[self setRequestMethod:@"POST"];
}
[self setPostLength:[[[NSFileManager defaultManager] fileAttributesAtPath:[self postBodyFilePath] traverseLink:NO] fileSize]];
// Otherwise, we have an in-memory request body
} else {
[self setPostLength:[postBody length]];
}
[self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",postLength]];
if (postBody && postLength > 0 && ![requestMethod isEqualToString:@"POST"] && ![requestMethod isEqualToString:@"PUT"]) {
if (postLength > 0 && ![requestMethod isEqualToString:@"POST"] && ![requestMethod isEqualToString:@"PUT"]) {
[self setRequestMethod:@"POST"];
}
}
haveBuiltPostBody = YES;
}
// Sets up storage for the post body
- (void)setupPostBody
{
if ([self shouldStreamPostDataFromDisk]) {
if (![self postBodyFilePath]) {
[self setPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
[self setDidCreateTemporaryPostDataFile:YES];
}
if (![self postBodyWriteStream]) {
[self setPostBodyWriteStream:[[[NSOutputStream alloc] initToFileAtPath:[self postBodyFilePath] append:NO] autorelease]];
... ... @@ -234,7 +247,7 @@ static NSError *ASIUnableToCreateRequestError;
}
// Call this method to get the received data as an NSString. Don't use for Binary data!
// Call this method to get the received data as an NSString. Don't use for binary data!
- (NSString *)responseString
{
NSData *data = [self responseData];
... ... @@ -341,7 +354,7 @@ static NSError *ASIUnableToCreateRequestError;
// Add custom headers
NSDictionary *headers;
//Add headers from the main request if this is a HEAD request generated by an ASINetwork Queue
//Add headers from the main request if this is a HEAD request generated by an ASINetworkQueue
if ([self mainRequest]) {
headers = [mainRequest requestHeaders];
} else {
... ... @@ -440,14 +453,11 @@ static NSError *ASIUnableToCreateRequestError;
}
}
// Start the request
// This is the 'main loop' for the request. Basically, it runs the runloop that our network stuff is attached to, and checks to see if we should cancel or timeout
- (void)loadRequest
{
[self startRequest];
// Record when the request started, so we can timeout if nothing happens
[self setLastActivityTime:[NSDate date]];
... ... @@ -523,6 +533,11 @@ static NSError *ASIUnableToCreateRequestError;
}
}
// Clean up any temporary file used to store request body for streaming
if ([self didCreateTemporaryPostDataFile]) {
[self removePostDataFile];
}
[self setResponseHeaders:nil];
[cancelledLock unlock];
}
... ... @@ -538,6 +553,15 @@ static NSError *ASIUnableToCreateRequestError;
}
}
- (void)removePostDataFile
{
NSError *removeError = nil;
[[NSFileManager defaultManager] removeItemAtPath:postBodyFilePath error:&removeError];
if (removeError) {
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at %@ with error: %@",postBodyFilePath,removeError],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]]];
}
}
#pragma mark upload/download progress
... ... @@ -618,6 +642,7 @@ static NSError *ASIUnableToCreateRequestError;
}
// If this is the first time we've written to the buffer, byteCount will be the size of the buffer (currently seems to be 128KB on both Mac and iPhone)
// If request body is less than 128KB, byteCount will be the total size of the request body
// We will remove this from any progress display, as kCFStreamPropertyHTTPRequestBytesWrittenCount does not tell us how much data has actually be written
if (totalBytesSent > 0 && uploadBufferSize == 0 && totalBytesSent != postLength) {
[self setUploadBufferSize:totalBytesSent];
... ... @@ -637,6 +662,10 @@ static NSError *ASIUnableToCreateRequestError;
[cancelledLock unlock];
if (totalBytesSent == 0) {
return;
}
if (uploadProgressDelegate) {
... ... @@ -1222,6 +1251,11 @@ static NSError *ASIUnableToCreateRequestError;
NSError *fileError = nil;
// Delete up the request body temporary file, if it exists
if (didCreateTemporaryPostDataFile) {
[self removePostDataFile];
}
// Close the output stream as we're done writing to the file
if (temporaryFileDownloadPath) {
[outputStream close];
... ... @@ -1547,4 +1581,5 @@ static NSError *ASIUnableToCreateRequestError;
@synthesize postBodyWriteStream;
@synthesize postBodyReadStream;
@synthesize shouldStreamPostDataFromDisk;
@synthesize didCreateTemporaryPostDataFile;
@end
... ...
... ... @@ -3,7 +3,7 @@
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
//
... ...
... ... @@ -3,7 +3,7 @@
// asi-http-request
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
//
#import "ASINetworkQueue.h"
... ...
... ... @@ -13,7 +13,7 @@
@implementation ASIHTTPRequestTests
/*
- (void)testBasicDownload
{
... ... @@ -188,6 +188,8 @@
GHAssertTrue(success,@"Failed to properly increment download progress %f != 1.0",progress);
}
*/
- (void)setProgress:(float)newProgress;
{
... ... @@ -207,8 +209,50 @@
GHAssertTrue(success,@"Failed to properly increment upload progress %f != 1.0",progress);
}
- (void)testPostBodyStreamedFromDisk
{
NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com/ignore"];
NSString *requestContentPath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"testfile.txt"];
[[NSMutableData dataWithLength:1024*32] writeToFile:requestContentPath atomically:NO];
// Test using a user-specified file as the request body (useful for PUT)
progress = 0;
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request setRequestMethod:@"PUT"];
[request setShouldStreamPostDataFromDisk:YES];
[request setUploadProgressDelegate:self];
[request setPostBodyFilePath:requestContentPath];
//[request start];
//Wait for 1 second
//[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
BOOL success = (progress > 0.95);
//GHAssertTrue(success,@"Failed to properly increment upload progress %f != 1.0",progress);
// Test building a request body by appending data
progress = 0;
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request setShouldStreamPostDataFromDisk:YES];
[request setUploadProgressDelegate:self];
NSData *d = [@"Below this will be the file I am posting:\r\n" dataUsingEncoding:NSUTF8StringEncoding];
[request appendPostData:[NSData dataWithBytes:[d bytes] length:[d length]]];
[request appendPostDataFromFile:requestContentPath];
[request start];
//Wait for 1 second
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
success = (progress > 0.95);
GHAssertTrue(success,@"Failed to properly increment upload progress %f != 1.0",progress);
}
/*
- (void)testCookies
{
... ... @@ -530,6 +574,6 @@
success = success = (progress > 0.95);
GHAssertTrue(success,@"Failed to correctly display increment progress for a partial download");
}
*/
@end
... ...