Permalink
Browse files

* Prevent timeouts in low bandwidth situations when upload size > 128KB

* Ignore first 128KB of upload progress in upload progress delegates, to prevent erroneous reporting of progress
* Remove silly debug code that broke accurate progress on iPhone
  • Loading branch information...
pokeb committed Dec 31, 2008
1 parent be6557a commit 0279004c9637785ce4dfc3ec4833590a544c03f7
View
@@ -10,7 +10,11 @@
// Portions are based on the ImageClient example from Apple:
// See: http://developer.apple.com/samplecode/ImageClient/listing37.html
-#import <CFNetwork/CFNetwork.h>
+
+// Dammit, importing frameworks when you are targetting two platforms is a PITA
+#if TARGET_OS_IPHONE
+ #import <CFNetwork/CFNetwork.h>
+#endif
typedef enum _ASINetworkErrorType {
ASIConnectionFailureErrorType = 1,
@@ -160,6 +164,8 @@ typedef enum _ASINetworkErrorType {
// Prevents the body of the post being built more than once (largely for subclasses)
BOOL haveBuiltPostBody;
+
+ unsigned long long uploadBufferSize;
}
#pragma mark init / dealloc
@@ -298,4 +304,5 @@ typedef enum _ASINetworkErrorType {
@property (retain) ASIHTTPRequest *mainRequest;
@property (assign) BOOL showAccurateProgress;
@property (assign,readonly) unsigned long long totalBytesRead;
+@property (assign) unsigned long long uploadBufferSize;
@end
View
@@ -71,6 +71,7 @@ - (id)initWithURL:(NSURL *)newURL
requestAuthentication = NULL;
haveBuiltPostBody = NO;
request = NULL;
+ [self setUploadBufferSize:0];
[self setResponseHeaders:nil];
[self setTimeOutSeconds:10];
[self setUseKeychainPersistance:NO];
@@ -184,6 +185,11 @@ - (void)main
complete = NO;
+ if (!url) {
+ [self failWithError:ASIUnableToCreateRequestError];
+ return;
+ }
+
// Create a new HTTP request.
request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)requestMethod, (CFURLRef)url, kCFHTTPVersion1_1);
if (!request) {
@@ -346,8 +352,12 @@ - (void)loadRequest
NSDate *now = [NSDate date];
// See if we need to timeout
- if (lastActivityTime && timeOutSeconds > 0) {
- if ([now timeIntervalSinceDate:lastActivityTime] > timeOutSeconds) {
+ if (lastActivityTime && timeOutSeconds > 0 && [now timeIntervalSinceDate:lastActivityTime] > timeOutSeconds) {
+
+ // Prevent timeouts before 128KB has been sent when the size of data to upload is greater than 128KB
+ // This is to workaround the fact that kCFStreamPropertyHTTPRequestBytesWrittenCount is the amount written to the buffer, not the amount actually sent
+ // This workaround prevents erroneous timeouts in low bandwidth situations (eg iPhone)
+ if (contentLength <= uploadBufferSize || (uploadBufferSize > 0 && lastBytesSent > uploadBufferSize)) {
[self failWithError:ASIRequestTimedOutError];
[self cancelLoad];
complete = YES;
@@ -474,6 +484,25 @@ - (void)updateUploadProgress
return;
}
unsigned long long byteCount = [[(NSNumber *)CFReadStreamCopyProperty (readStream, kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease] unsignedLongLongValue];
+
+ // 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)
+ // We will remove this from any progress display, as kCFStreamPropertyHTTPRequestBytesWrittenCount does not tell us how much data has actually be written
+ if (byteCount > 0 && uploadBufferSize == 0 && byteCount != postLength) {
+ [self setUploadBufferSize:byteCount];
+ SEL selector = @selector(setUploadBufferSize:);
+ if ([uploadProgressDelegate respondsToSelector:selector]) {
+ NSMethodSignature *signature = nil;
+ signature = [[uploadProgressDelegate class] instanceMethodSignatureForSelector:selector];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
+ [invocation setTarget:uploadProgressDelegate];
+ [invocation setSelector:selector];
+ [invocation setArgument:&byteCount atIndex:2];
+ [invocation invoke];
+ }
+ }
+
+
+
[cancelledLock unlock];
if (byteCount > lastBytesSent) {
[self setLastActivityTime:[NSDate date]];
@@ -485,7 +514,13 @@ - (void)updateUploadProgress
if ([uploadProgressDelegate respondsToSelector:@selector(incrementUploadProgressBy:)]) {
unsigned long long value = 0;
if (showAccurateProgress) {
- value = byteCount-lastBytesSent;
+ if (byteCount == postLength) {
+ value = byteCount+uploadBufferSize;
+ } else if (lastBytesSent > 0) {
+ value = ((byteCount-uploadBufferSize)-(lastBytesSent-uploadBufferSize));
+ } else {
+ value = 0;
+ }
} else {
value = 1;
updatedProgress = YES;
@@ -501,7 +536,7 @@ - (void)updateUploadProgress
// We aren't using a queue, we should just set progress of the indicator
} else {
- [ASIHTTPRequest setProgress:(double)(1.0*byteCount/postLength) forProgressIndicator:uploadProgressDelegate];
+ [ASIHTTPRequest setProgress:(double)(1.0*(byteCount-uploadBufferSize)/(postLength-uploadBufferSize)) forProgressIndicator:uploadProgressDelegate];
}
}
@@ -602,7 +637,7 @@ -(void)removeUploadProgressSoFar
+ (void)setProgress:(double)progress forProgressIndicator:(id)indicator
{
-
+
SEL selector;
[progressLock lock];
@@ -614,13 +649,12 @@ + (void)setProgress:(double)progress forProgressIndicator:(id)indicator
[invocation setSelector:selector];
float progressFloat = (float)progress; // UIProgressView wants a float for the progress parameter
[invocation setArgument:&progressFloat atIndex:2];
- [invocation invokeWithTarget:indicator];
-
+
// If we're running in the main thread, update the progress straight away. Otherwise, it's not that urgent
[invocation performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:indicator waitUntilDone:[NSThread isMainThread]];
+
-
- // Cocoa: NSProgressIndicator
+ // Cocoa: NSProgressIndicator
} else if ([indicator respondsToSelector:@selector(setDoubleValue:)]) {
selector = @selector(setDoubleValue:);
NSMethodSignature *signature = [[indicator class] instanceMethodSignatureForSelector:selector];
@@ -1146,4 +1180,5 @@ + (void)clearSession
@synthesize totalBytesRead;
@synthesize showAccurateProgress;
@synthesize totalBytesRead;
+@synthesize uploadBufferSize;
@end
View
@@ -74,6 +74,11 @@
// Called during a request when authorisation fails to cancel any progress so far
- (void)decrementUploadProgressBy:(unsigned long long)bytes;
+// Called when the first chunk of data is written to the upload buffer
+// We ignore the first part chunk when tracking upload progress, as kCFStreamPropertyHTTPRequestBytesWrittenCount reports the amount of data written to the buffer, not the amount sent
+// This is to workaround the first 128KB of data appearing in an upload progress delegate immediately
+- (void)setUploadBufferSize:(unsigned long long)bytes;
+
// All ASINetworkQueues are paused when created so that total size can be calculated before the queue starts
// This method will start the queue
- (void)go;
View
@@ -193,6 +193,16 @@ - (void)requestDidFinish:(ASIHTTPRequest *)request
}
}
+
+- (void)setUploadBufferSize:(unsigned long long)bytes
+{
+ if (!uploadProgressDelegate) {
+ return;
+ }
+ uploadProgressTotalBytes -= bytes;
+ [self incrementUploadProgressBy:0];
+}
+
- (void)incrementUploadSizeBy:(unsigned long long)bytes
{
if (!uploadProgressDelegate) {
View
@@ -1,20 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
-<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.02">
+<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.03">
<data>
<int key="IBDocument.SystemTarget">1050</int>
- <string key="IBDocument.SystemVersion">9F33</string>
- <string key="IBDocument.InterfaceBuilderVersion">672</string>
- <string key="IBDocument.AppKitVersion">949.34</string>
- <string key="IBDocument.HIToolboxVersion">352.00</string>
+ <string key="IBDocument.SystemVersion">9G55</string>
+ <string key="IBDocument.InterfaceBuilderVersion">677</string>
+ <string key="IBDocument.AppKitVersion">949.43</string>
+ <string key="IBDocument.HIToolboxVersion">353.00</string>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool>
- <integer value="440"/>
+ <integer value="444"/>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilderKit</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ </object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSCustomObject" id="1021">
@@ -1055,7 +1064,7 @@ cmVhZC4</string>
<object class="NSTabViewItem" id="725395930">
<string key="NSIdentifier">Item 2</string>
<object class="NSView" key="NSView" id="624078948">
- <reference key="NSNextResponder" ref="495298985"/>
+ <nil key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
@@ -1208,7 +1217,6 @@ cmVhZC4</string>
</object>
</object>
<string key="NSFrame">{{10, 33}, {441, 243}}</string>
- <reference key="NSSuperview" ref="495298985"/>
</object>
<string key="NSLabel">Queue</string>
<reference key="NSColor" ref="482475293"/>
@@ -1323,7 +1331,7 @@ c3N3b3JkLCBlbnRlciAndG9wc2VjcmV0JyBmb3IgYm90aC4</string>
<object class="NSTabViewItem" id="409502867">
<string key="NSIdentifier">Item 4</string>
<object class="NSView" key="NSView" id="408528501">
- <nil key="NSNextResponder"/>
+ <reference key="NSNextResponder" ref="495298985"/>
<int key="NSvFlags">256</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
@@ -1365,20 +1373,21 @@ c3N3b3JkLCBlbnRlciAndG9wc2VjcmV0JyBmb3IgYm90aC4</string>
</object>
</object>
<string key="NSFrame">{{10, 33}, {441, 243}}</string>
+ <reference key="NSSuperview" ref="495298985"/>
</object>
<string key="NSLabel">Upload</string>
<reference key="NSColor" ref="482475293"/>
<reference key="NSTabView" ref="495298985"/>
</object>
</object>
- <reference key="NSSelectedTabViewItem" ref="725395930"/>
+ <reference key="NSSelectedTabViewItem" ref="409502867"/>
<reference key="NSFont" ref="584670792"/>
<int key="NSTvFlags">0</int>
<bool key="NSAllowTruncatedLabels">YES</bool>
<bool key="NSDrawsBackground">YES</bool>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
- <reference ref="624078948"/>
+ <reference ref="408528501"/>
</object>
</object>
</object>
@@ -3420,15 +3429,13 @@ c3N3b3JkLCBlbnRlciAndG9wc2VjcmV0JyBmb3IgYm90aC4</string>
<string>354.IBPluginDependency</string>
<string>354.ImportedFromIB2</string>
<string>371.IBEditorWindowLastContentRect</string>
- <string>371.IBPluginDependency</string>
<string>371.IBWindowTemplateEditedContentRect</string>
<string>371.NSWindowTemplate.visibleAtLaunch</string>
<string>371.editorWindowContentRectSynchronizationRect</string>
<string>372.IBPluginDependency</string>
<string>381.IBPluginDependency</string>
<string>384.IBPluginDependency</string>
<string>390.IBEditorWindowLastContentRect</string>
- <string>390.IBPluginDependency</string>
<string>390.IBWindowTemplateEditedContentRect</string>
<string>390.NSWindowTemplate.visibleAtLaunch</string>
<string>390.editorWindowContentRectSynchronizationRect</string>
@@ -3458,6 +3465,12 @@ c3N3b3JkLCBlbnRlciAndG9wc2VjcmV0JyBmb3IgYm90aC4</string>
<string>436.IBPluginDependency</string>
<string>437.IBPluginDependency</string>
<string>438.IBPluginDependency</string>
+ <string>439.IBPluginDependency</string>
+ <string>440.IBPluginDependency</string>
+ <string>441.IBPluginDependency</string>
+ <string>442.IBPluginDependency</string>
+ <string>443.IBPluginDependency</string>
+ <string>444.IBPluginDependency</string>
<string>451.IBPluginDependency</string>
<string>452.IBPluginDependency</string>
<string>453.IBPluginDependency</string>
@@ -3673,15 +3686,13 @@ c3N3b3JkLCBlbnRlciAndG9wc2VjcmV0JyBmb3IgYm90aC4</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<reference ref="9"/>
<string>{{267, 307}, {480, 337}}</string>
- <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{267, 307}, {480, 337}}</string>
<reference ref="9"/>
<string>{{235, -51}, {480, 337}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{21, 629}, {279, 182}}</string>
- <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{21, 629}, {279, 182}}</string>
<integer value="0"/>
<string>{{793, -129}, {279, 182}}</string>
@@ -3746,6 +3757,12 @@ c3N3b3JkLCBlbnRlciAndG9wc2VjcmV0JyBmb3IgYm90aC4</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<reference ref="9"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
View
@@ -9,7 +9,7 @@ It provides:
* Easy access to request and response HTTP headers
* Progress delegates (NSProgressIndicators and UIProgressViews) to show information about download AND upload progress
* Auto-magic management of upload and download progress indicators for operation queues
-* Basic + Digest authentication support, credentials are automatically for the duration of a session, and can be stored for later in the Keychain.
+* Basic + Digest authentication support, credentials are automatically re-used for the duration of a session, and can be stored for later in the Keychain.
* Cookie support
* Based on NSOperation to make queuing requests and background operation easy
* Basic unit tests (more to come!)
View
@@ -0,0 +1,20 @@
+//
+// UploadViewController.h
+// asi-http-request
+//
+// Created by Ben Copsey on 31/12/2008.
+// Copyright 2008 All-Seeing Interactive. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class ASINetworkQueue;
+
+@interface UploadViewController : UIViewController {
+ ASINetworkQueue *networkQueue;
+ IBOutlet UIProgressView *progressIndicator;
+}
+
+- (IBAction)performLargeUpload:(id)sender;
+
+@end
View
@@ -0,0 +1,44 @@
+//
+// UploadViewController.m
+// asi-http-request
+//
+// Created by Ben Copsey on 31/12/2008.
+// Copyright 2008 All-Seeing Interactive. All rights reserved.
+//
+
+#import "UploadViewController.h"
+#import "ASIFormDataRequest.h"
+#import "ASINetworkQueue.h"
+
+@implementation UploadViewController
+
+
+- (void)awakeFromNib
+{
+ networkQueue = [[ASINetworkQueue alloc] init];
+}
+
+- (IBAction)performLargeUpload:(id)sender
+{
+ [networkQueue cancelAllOperations];
+ [networkQueue setShowAccurateProgress:YES];
+ [networkQueue setUploadProgressDelegate:progressIndicator];
+ [networkQueue setDelegate:self];
+
+ ASIFormDataRequest *request = [[[ASIFormDataRequest alloc] initWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ignore"]] autorelease];
+ [request setPostValue:@"test" forKey:@"value1"];
+ [request setPostValue:@"test" forKey:@"value2"];
+ [request setPostValue:@"test" forKey:@"value3"];
+ [request setTimeOutSeconds:20];
+ [request setData:[NSMutableData dataWithLength:1024*1024] forKey:@"1mb-of-crap"];
+
+ [networkQueue addOperation:request];
+ [networkQueue go];
+}
+
+- (void)dealloc {
+ [networkQueue release];
+ [super dealloc];
+}
+
+@end
Oops, something went wrong.

0 comments on commit 0279004

Please sign in to comment.