Ben Copsey

Initial stuff for bandwidth throttling

@@ -31,8 +31,14 @@ typedef enum _ASINetworkErrorType { @@ -31,8 +31,14 @@ typedef enum _ASINetworkErrorType {
31 31
32 } ASINetworkErrorType; 32 } ASINetworkErrorType;
33 33
  34 +// The error domain that all errors generated by ASIHTTPRequest use
34 extern NSString* const NetworkRequestErrorDomain; 35 extern NSString* const NetworkRequestErrorDomain;
35 36
  37 +// You can use this number to throttle upload and download bandwidth in iPhone OS apps send or receive a large amount of data
  38 +// This may help apps that might otherwise be rejected for inclusion into the app store for using excessive bandwidth
  39 +// This number is not official, as far as I know there is no officially documented bandwidth limit
  40 +extern unsigned long const ASIWWANBandwidthThrottleAmount;
  41 +
36 @interface ASIHTTPRequest : NSOperation { 42 @interface ASIHTTPRequest : NSOperation {
37 43
38 // The url for this operation, should include GET params in the query string where appropriate 44 // The url for this operation, should include GET params in the query string where appropriate
@@ -449,6 +455,15 @@ extern NSString* const NetworkRequestErrorDomain; @@ -449,6 +455,15 @@ extern NSString* const NetworkRequestErrorDomain;
449 // Only works on Mac OS, will always return 'application/octet-stream' on iPhone 455 // Only works on Mac OS, will always return 'application/octet-stream' on iPhone
450 + (NSString *)mimeTypeForFileAtPath:(NSString *)path; 456 + (NSString *)mimeTypeForFileAtPath:(NSString *)path;
451 457
  458 +#pragma mark bandwidth throttling
  459 +
  460 +// The maximum number of bytes ALL requests can send / receive in a second
  461 +// This is a rough figure. The actual amount used may be slightly more
  462 ++ (unsigned long)maxBandwidthPerSecond;
  463 ++ (void)setMaxBandwidthPerSecond:(unsigned long)bytes;
  464 +
  465 +
  466 +
452 @property (retain) NSString *username; 467 @property (retain) NSString *username;
453 @property (retain) NSString *password; 468 @property (retain) NSString *password;
454 @property (retain) NSString *domain; 469 @property (retain) NSString *domain;
@@ -48,12 +48,29 @@ static NSError *ASIAuthenticationError; @@ -48,12 +48,29 @@ static NSError *ASIAuthenticationError;
48 static NSError *ASIUnableToCreateRequestError; 48 static NSError *ASIUnableToCreateRequestError;
49 static NSError *ASITooMuchRedirectionError; 49 static NSError *ASITooMuchRedirectionError;
50 50
  51 +// Used for throttling bandwidth
  52 +// I am assuming you don't have a connection capable of more than 4GB/second? If so, why are you reading this? Aren't you supposed to be backing up the Internet?!
  53 +static unsigned long bandwidthUsedInLastSecond = 0;
  54 +
  55 +// A date one second in the future from the time it was created
  56 +static NSDate *bandwidthThrottlingMeasurementDate = nil;
  57 +
  58 +// Since throttling variables are shared among all requests, we'll use a lock to mediate access
  59 +static NSLock *bandwidthThrottlingLock = nil;
  60 +
  61 +// the maximum number of bytes that can be transmitted in one second
  62 +static unsigned long maxBandwidthPerSecond = 0;
  63 +
  64 +// A default figure for throttling bandwidth on mobile devices
  65 +unsigned long const ASIWWANBandwidthThrottleAmount = 14800;
51 66
52 // Private stuff 67 // Private stuff
53 @interface ASIHTTPRequest () 68 @interface ASIHTTPRequest ()
54 69
55 - (BOOL)askDelegateForCredentials; 70 - (BOOL)askDelegateForCredentials;
56 - (BOOL)askDelegateForProxyCredentials; 71 - (BOOL)askDelegateForProxyCredentials;
  72 ++ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes;
  73 ++ (void)throttleBandwidth;
57 74
58 @property (assign) BOOL complete; 75 @property (assign) BOOL complete;
59 @property (retain) NSDictionary *responseHeaders; 76 @property (retain) NSDictionary *responseHeaders;
@@ -97,6 +114,7 @@ static NSError *ASITooMuchRedirectionError; @@ -97,6 +114,7 @@ static NSError *ASITooMuchRedirectionError;
97 { 114 {
98 if (self == [ASIHTTPRequest class]) { 115 if (self == [ASIHTTPRequest class]) {
99 progressLock = [[NSRecursiveLock alloc] init]; 116 progressLock = [[NSRecursiveLock alloc] init];
  117 + bandwidthThrottlingLock = [[NSLock alloc] init];
100 ASIRequestTimedOutError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request timed out",NSLocalizedDescriptionKey,nil]] retain]; 118 ASIRequestTimedOutError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request timed out",NSLocalizedDescriptionKey,nil]] retain];
101 ASIAuthenticationError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed",NSLocalizedDescriptionKey,nil]] retain]; 119 ASIAuthenticationError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed",NSLocalizedDescriptionKey,nil]] retain];
102 ASIRequestCancelledError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled",NSLocalizedDescriptionKey,nil]] retain]; 120 ASIRequestCancelledError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled",NSLocalizedDescriptionKey,nil]] retain];
@@ -618,7 +636,6 @@ static NSError *ASITooMuchRedirectionError; @@ -618,7 +636,6 @@ static NSError *ASITooMuchRedirectionError;
618 [self startRequest]; 636 [self startRequest];
619 637
620 638
621 -  
622 // Wait for the request to finish 639 // Wait for the request to finish
623 while (!complete) { 640 while (!complete) {
624 641
@@ -665,6 +682,11 @@ static NSError *ASITooMuchRedirectionError; @@ -665,6 +682,11 @@ static NSError *ASITooMuchRedirectionError;
665 682
666 // Find out if we've sent any more data than last time, and reset the timeout if so 683 // Find out if we've sent any more data than last time, and reset the timeout if so
667 if (totalBytesSent > lastBytesSent) { 684 if (totalBytesSent > lastBytesSent) {
  685 +
  686 + // For bandwidth throttling
  687 + if ([ASIHTTPRequest maxBandwidthPerSecond] > 0) {
  688 + [ASIHTTPRequest incrementBandwidthUsedInLastSecond:totalBytesSent-maxBandwidthPerSecond];
  689 + }
668 [self setLastActivityTime:[NSDate date]]; 690 [self setLastActivityTime:[NSDate date]];
669 [self setLastBytesSent:totalBytesSent]; 691 [self setLastBytesSent:totalBytesSent];
670 } 692 }
@@ -676,7 +698,8 @@ static NSError *ASITooMuchRedirectionError; @@ -676,7 +698,8 @@ static NSError *ASITooMuchRedirectionError;
676 698
677 [self updateProgressIndicators]; 699 [self updateProgressIndicators];
678 700
679 - 701 + // Throttle bandwidth if nescessary
  702 + [ASIHTTPRequest throttleBandwidth];
680 703
681 // This thread should wait for 1/4 second for the stream to do something. We'll stop early if it does. 704 // This thread should wait for 1/4 second for the stream to do something. We'll stop early if it does.
682 CFRunLoopRunInMode(ASIHTTPRequestRunMode,0.25,YES); 705 CFRunLoopRunInMode(ASIHTTPRequestRunMode,0.25,YES);
@@ -1630,6 +1653,11 @@ static NSError *ASITooMuchRedirectionError; @@ -1630,6 +1653,11 @@ static NSError *ASITooMuchRedirectionError;
1630 [self setTotalBytesRead:[self totalBytesRead]+bytesRead]; 1653 [self setTotalBytesRead:[self totalBytesRead]+bytesRead];
1631 [self setLastActivityTime:[NSDate date]]; 1654 [self setLastActivityTime:[NSDate date]];
1632 1655
  1656 + // For bandwidth throttling
  1657 + if ([ASIHTTPRequest maxBandwidthPerSecond] > 0) {
  1658 + [ASIHTTPRequest incrementBandwidthUsedInLastSecond:bytesRead];
  1659 + }
  1660 +
1633 // Are we downloading to a file? 1661 // Are we downloading to a file?
1634 if ([self downloadDestinationPath]) { 1662 if ([self downloadDestinationPath]) {
1635 if (![self fileDownloadOutputStream]) { 1663 if (![self fileDownloadOutputStream]) {
@@ -2219,7 +2247,7 @@ static NSError *ASITooMuchRedirectionError; @@ -2219,7 +2247,7 @@ static NSError *ASITooMuchRedirectionError;
2219 return proxies; 2247 return proxies;
2220 } 2248 }
2221 2249
2222 -#pragma mark Helper functions 2250 +#pragma mark mime-type detection
2223 2251
2224 + (NSString *)mimeTypeForFileAtPath:(NSString *)path 2252 + (NSString *)mimeTypeForFileAtPath:(NSString *)path
2225 { 2253 {
@@ -2251,6 +2279,55 @@ static NSError *ASITooMuchRedirectionError; @@ -2251,6 +2279,55 @@ static NSError *ASITooMuchRedirectionError;
2251 #endif 2279 #endif
2252 } 2280 }
2253 2281
  2282 +#pragma mark bandwidth throttling
  2283 +
  2284 ++ (unsigned long)maxBandwidthPerSecond
  2285 +{
  2286 + [bandwidthThrottlingLock lock];
  2287 + unsigned long amount = maxBandwidthPerSecond;
  2288 + [bandwidthThrottlingLock unlock];
  2289 + return amount;
  2290 +}
  2291 +
  2292 ++ (void)setMaxBandwidthPerSecond:(unsigned long)bytes
  2293 +{
  2294 + [bandwidthThrottlingLock lock];
  2295 + maxBandwidthPerSecond = bytes;
  2296 + [bandwidthThrottlingLock unlock];
  2297 +}
  2298 +
  2299 ++ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes
  2300 +{
  2301 + [bandwidthThrottlingLock lock];
  2302 + bandwidthUsedInLastSecond += bytes;
  2303 + [bandwidthThrottlingLock unlock];
  2304 +}
  2305 +
  2306 ++ (void)throttleBandwidth
  2307 +{
  2308 + // Other requests may have to wait for this lock if we're sleeping, but this is fine, since we already know they shouldn't be sending or receiving data
  2309 + [bandwidthThrottlingLock lock];
  2310 +
  2311 + // Are we performing bandwidth throttling?
  2312 + if (maxBandwidthPerSecond > 0) {
  2313 + if (!bandwidthThrottlingMeasurementDate) {
  2314 + bandwidthThrottlingMeasurementDate = [NSDate dateWithTimeIntervalSinceNow:1];
  2315 + }
  2316 +
  2317 + // How much data can we still send or receive this second?
  2318 + long long bytesRemaining = maxBandwidthPerSecond - bandwidthUsedInLastSecond;
  2319 +
  2320 + // Have we used up our allowance?
  2321 + if (bytesRemaining < 0) {
  2322 +
  2323 + // Yes, put this request to sleep until a second is up
  2324 + [NSThread sleepUntilDate:bandwidthThrottlingMeasurementDate];
  2325 + bandwidthThrottlingMeasurementDate = [NSDate dateWithTimeIntervalSinceNow:1];
  2326 + }
  2327 + }
  2328 + [bandwidthThrottlingLock unlock];
  2329 +}
  2330 +
2254 2331
2255 2332
2256 @synthesize username; 2333 @synthesize username;
@@ -2324,7 +2401,6 @@ static NSError *ASITooMuchRedirectionError; @@ -2324,7 +2401,6 @@ static NSError *ASITooMuchRedirectionError;
2324 @synthesize authenticationLock; 2401 @synthesize authenticationLock;
2325 @synthesize needsProxyAuthentication; 2402 @synthesize needsProxyAuthentication;
2326 @synthesize proxyCredentials; 2403 @synthesize proxyCredentials;
2327 -  
2328 @synthesize proxyHost; 2404 @synthesize proxyHost;
2329 @synthesize proxyPort; 2405 @synthesize proxyPort;
2330 @synthesize PACurl; 2406 @synthesize PACurl;