Ben Copsey

Added support for persistent http connections

More performance tests
@@ -331,6 +331,14 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; @@ -331,6 +331,14 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
331 331
332 // The number of times this request has retried (when numberOfTimesToRetryOnTimeout > 0) 332 // The number of times this request has retried (when numberOfTimesToRetryOnTimeout > 0)
333 int retryCount; 333 int retryCount;
  334 +
  335 + // When YES, requests will keep the connection to the server alive for a while to allow subsequent requests to re-use it for a substatial speed-boost
  336 + // Persistent connections only work when the server sends a 'Keep-Alive' header
  337 + // Default is YES
  338 + BOOL shouldAttemptPersistentConnection;
  339 +
  340 + // Set to yes when an appropriate keep-alive header is found
  341 + BOOL canUsePersistentConnection;
334 } 342 }
335 343
336 #pragma mark init / dealloc 344 #pragma mark init / dealloc
@@ -666,4 +674,5 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; @@ -666,4 +674,5 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
666 @property (assign) BOOL shouldRunInBackgroundThread; 674 @property (assign) BOOL shouldRunInBackgroundThread;
667 @property (assign) int numberOfTimesToRetryOnTimeout; 675 @property (assign) int numberOfTimesToRetryOnTimeout;
668 @property (assign, readonly) int retryCount; 676 @property (assign, readonly) int retryCount;
  677 +@property (assign) BOOL shouldAttemptPersistentConnection;
669 @end 678 @end
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 #import "ASIInputStream.h" 21 #import "ASIInputStream.h"
22 22
23 // Automatically set on build 23 // Automatically set on build
24 -NSString *ASIHTTPRequestVersion = @"v1.2-52 2009-12-19"; 24 +NSString *ASIHTTPRequestVersion = @"v1.2-53 2009-12-19";
25 25
26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; 26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
27 27
@@ -154,6 +154,7 @@ static BOOL isiPhoneOS2; @@ -154,6 +154,7 @@ static BOOL isiPhoneOS2;
154 @property (assign) BOOL isSynchronous; 154 @property (assign) BOOL isSynchronous;
155 @property (assign) BOOL inProgress; 155 @property (assign) BOOL inProgress;
156 @property (assign) int retryCount; 156 @property (assign) int retryCount;
  157 +@property (assign) BOOL canUsePersistentConnection;
157 @end 158 @end
158 159
159 160
@@ -191,6 +192,7 @@ static BOOL isiPhoneOS2; @@ -191,6 +192,7 @@ static BOOL isiPhoneOS2;
191 self = [super init]; 192 self = [super init];
192 [self setRequestMethod:@"GET"]; 193 [self setRequestMethod:@"GET"];
193 194
  195 + [self setShouldAttemptPersistentConnection:YES];
194 [self setShouldPresentCredentialsBeforeChallenge:YES]; 196 [self setShouldPresentCredentialsBeforeChallenge:YES];
195 [self setShouldRedirect:YES]; 197 [self setShouldRedirect:YES];
196 [self setShowAccurateProgress:YES]; 198 [self setShowAccurateProgress:YES];
@@ -808,6 +810,9 @@ static BOOL isiPhoneOS2; @@ -808,6 +810,9 @@ static BOOL isiPhoneOS2;
808 CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, [NSMutableDictionary dictionaryWithObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]); 810 CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, [NSMutableDictionary dictionaryWithObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]);
809 } 811 }
810 812
  813 + // Use a persistent connection if possible
  814 + CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
  815 +
811 816
812 // Handle proxy settings 817 // Handle proxy settings
813 818
@@ -930,10 +935,20 @@ static BOOL isiPhoneOS2; @@ -930,10 +935,20 @@ static BOOL isiPhoneOS2;
930 [self scheduleReadStream]; 935 [self scheduleReadStream];
931 } 936 }
932 937
933 - while (!complete) { 938 +// if ([NSThread isMainThread]) {
934 - [self checkRequestStatus]; 939 +// [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES];
935 - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, NO); 940 +// CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeOutSeconds, NO);
936 - } 941 +//
  942 +// } else if (!uploadProgressDelegate && !downloadProgressDelegate) {
  943 +// CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeOutSeconds, NO);
  944 +// [self checkRequestStatus];
  945 +// } else {
  946 +
  947 + while (!complete) {
  948 + [self checkRequestStatus];
  949 + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, NO);
  950 + }
  951 + //}
937 } 952 }
938 953
939 // This gets fired every 1/4 of a second in asynchronous requests to update the progress and work out if we need to timeout 954 // This gets fired every 1/4 of a second in asynchronous requests to update the progress and work out if we need to timeout
@@ -1594,11 +1609,32 @@ static BOOL isiPhoneOS2; @@ -1594,11 +1609,32 @@ static BOOL isiPhoneOS2;
1594 1609
1595 } 1610 }
1596 1611
  1612 + // Handle connection persistence
  1613 + if ([self shouldAttemptPersistentConnection] && [[[self responseHeaders] objectForKey:@"Connection"] isEqualToString:@"Keep-Alive"]) {
  1614 + NSString *keepAliveHeader = [[self responseHeaders] objectForKey:@"Keep-Alive"];
  1615 + if (keepAliveHeader) {
  1616 + int timeout = 0;
  1617 + int max = 0;
  1618 + NSScanner *scanner = [NSScanner scannerWithString:keepAliveHeader];
  1619 + [scanner scanString:@"timeout=" intoString:NULL];
  1620 + [scanner scanInt:&timeout];
  1621 + [scanner scanUpToString:@"max=" intoString:NULL];
  1622 + [scanner scanString:@"max=" intoString:NULL];
  1623 + [scanner scanInt:&max];
  1624 + if (max > 5) {
  1625 + [self setCanUsePersistentConnection:YES];
  1626 + CFRetain(readStream);
  1627 + [NSTimer scheduledTimerWithTimeInterval:max target:[self class] selector:@selector(closePersistentConnection:) userInfo:(id)readStream repeats:NO];
  1628 + }
  1629 + }
  1630 + }
1597 } 1631 }
  1632 +
  1633 +
  1634 +
1598 CFRelease(headers); 1635 CFRelease(headers);
1599 return isAuthenticationChallenge; 1636 return isAuthenticationChallenge;
1600 } 1637 }
1601 -  
1602 1638
1603 #pragma mark http authentication 1639 #pragma mark http authentication
1604 1640
@@ -2222,12 +2258,14 @@ static BOOL isiPhoneOS2; @@ -2222,12 +2258,14 @@ static BOOL isiPhoneOS2;
2222 if ([self needsRedirect]) { 2258 if ([self needsRedirect]) {
2223 return; 2259 return;
2224 } 2260 }
2225 - long long bufferSize = 2048; 2261 +// long long bufferSize = 2048;
2226 - if (contentLength > 262144) { 2262 +// if (contentLength > 262144) {
2227 - bufferSize = 65536; 2263 +// bufferSize = 65536;
2228 - } else if (contentLength > 65536) { 2264 +// } else if (contentLength > 65536) {
2229 - bufferSize = 16384; 2265 +// bufferSize = 16384;
2230 - } 2266 +// }
  2267 +
  2268 + long long bufferSize = 262144;
2231 2269
2232 // Reduce the buffer size if we're receiving data too quickly when bandwidth throttling is active 2270 // Reduce the buffer size if we're receiving data too quickly when bandwidth throttling is active
2233 // This just augments the throttling done in measureBandwidthUsage to reduce the amount we go over the limit 2271 // This just augments the throttling done in measureBandwidthUsage to reduce the amount we go over the limit
@@ -2397,7 +2435,9 @@ static BOOL isiPhoneOS2; @@ -2397,7 +2435,9 @@ static BOOL isiPhoneOS2;
2397 if (readStreamIsScheduled) { 2435 if (readStreamIsScheduled) {
2398 CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 2436 CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
2399 } 2437 }
2400 - CFReadStreamClose(readStream); 2438 + if (!canUsePersistentConnection) {
  2439 + CFReadStreamClose(readStream);
  2440 + }
2401 CFRelease(readStream); 2441 CFRelease(readStream);
2402 readStream = NULL; 2442 readStream = NULL;
2403 } 2443 }
@@ -2421,6 +2461,13 @@ static BOOL isiPhoneOS2; @@ -2421,6 +2461,13 @@ static BOOL isiPhoneOS2;
2421 } 2461 }
2422 } 2462 }
2423 2463
  2464 ++ (void)closePersistentConnection:(NSTimer *)timer
  2465 +{
  2466 + CFReadStreamRef stream = (CFReadStreamRef)[timer userInfo];
  2467 + CFReadStreamClose(stream);
  2468 + CFRelease(stream);
  2469 +}
  2470 +
2424 #pragma mark NSCopying 2471 #pragma mark NSCopying
2425 2472
2426 - (id)copyWithZone:(NSZone *)zone 2473 - (id)copyWithZone:(NSZone *)zone
@@ -3427,6 +3474,8 @@ static BOOL isiPhoneOS2; @@ -3427,6 +3474,8 @@ static BOOL isiPhoneOS2;
3427 @synthesize shouldRunInBackgroundThread; 3474 @synthesize shouldRunInBackgroundThread;
3428 @synthesize numberOfTimesToRetryOnTimeout; 3475 @synthesize numberOfTimesToRetryOnTimeout;
3429 @synthesize retryCount; 3476 @synthesize retryCount;
  3477 +@synthesize shouldAttemptPersistentConnection;
  3478 +@synthesize canUsePersistentConnection;
3430 @end 3479 @end
3431 3480
3432 3481
@@ -10,6 +10,8 @@ @@ -10,6 +10,8 @@
10 #import "ASITestCase.h" 10 #import "ASITestCase.h"
11 11
12 @interface PerformanceTests : ASITestCase { 12 @interface PerformanceTests : ASITestCase {
  13 + NSURL *testURL;
  14 +
13 NSDate *testStartDate; 15 NSDate *testStartDate;
14 int requestsComplete; 16 int requestsComplete;
15 NSMutableArray *responseData; 17 NSMutableArray *responseData;
@@ -19,6 +21,7 @@ @@ -19,6 +21,7 @@
19 - (void)testASIHTTPRequestAsyncPerformance; 21 - (void)testASIHTTPRequestAsyncPerformance;
20 - (void)testNSURLConnectionAsyncPerformance; 22 - (void)testNSURLConnectionAsyncPerformance;
21 23
  24 +@property (retain,nonatomic) NSURL *testURL;
22 @property (retain,nonatomic) NSDate *testStartDate; 25 @property (retain,nonatomic) NSDate *testStartDate;
23 @property (assign,nonatomic) int requestsComplete; 26 @property (assign,nonatomic) int requestsComplete;
24 @property (retain,nonatomic) NSMutableArray *responseData; 27 @property (retain,nonatomic) NSMutableArray *responseData;
@@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
9 #import "PerformanceTests.h" 9 #import "PerformanceTests.h"
10 #import "ASIHTTPRequest.h" 10 #import "ASIHTTPRequest.h"
11 11
  12 +
12 @interface NSURLConnectionSubclass : NSURLConnection { 13 @interface NSURLConnectionSubclass : NSURLConnection {
13 int tag; 14 int tag;
14 } 15 }
@@ -21,11 +22,105 @@ @@ -21,11 +22,105 @@
21 22
22 @implementation PerformanceTests 23 @implementation PerformanceTests
23 24
  25 +- (void)setUp
  26 +{
  27 + [self setTestURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
  28 +}
  29 +
  30 +- (void)testASIHTTPRequestSynchronousPerformance
  31 +{
  32 + [self performSelectorOnMainThread:@selector(runSynchronousASIHTTPRequests) withObject:nil waitUntilDone:YES];
  33 +}
  34 +
  35 +
  36 +- (void)runSynchronousASIHTTPRequests
  37 +{
  38 + int runTimes = 10;
  39 + NSTimeInterval times[runTimes];
  40 + int i;
  41 + for (i=0; i<runTimes; i++) {
  42 + NSDate *startTime = [NSDate date];
  43 + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:testURL];
  44 + //Send the same headers as NSURLRequest
  45 + [request addRequestHeader:@"Pragma" value:@"no-cache"];
  46 + [request addRequestHeader:@"Accept" value:@"*/*"];
  47 + [request addRequestHeader:@"Accept-Language" value:@"en/us"];
  48 + [request setUseCookiePersistance:NO];
  49 + [request setUseSessionPersistance:NO];
  50 + //[request setShouldRunInBackgroundThread:YES];
  51 + [request startSynchronous];
  52 + if ([request error]) {
  53 + NSLog(@"Request failed - cannot proceed with test");
  54 + return;
  55 + }
  56 + times[i] = [[NSDate date] timeIntervalSinceDate:startTime];
  57 + }
  58 + NSTimeInterval bestTime = 1000;
  59 + NSTimeInterval worstTime = 0;
  60 + NSTimeInterval totalTime = 0;
  61 + for (i=0; i<runTimes; i++) {
  62 + if (times[i] < bestTime) {
  63 + bestTime = times[i];
  64 + }
  65 + if (times[i] > worstTime) {
  66 + worstTime = times[i];
  67 + }
  68 + totalTime += times[i];
  69 + }
  70 + NSLog(@"Ran runTimes requests in %f seconds (average time: %f secs / best time: %f secs / worst time: %f secs)",totalTime,totalTime/runTimes,bestTime,worstTime);
  71 +}
  72 +
  73 +
  74 +- (void)testNSURLConnectionSynchronousPerformance
  75 +{
  76 + [self performSelectorOnMainThread:@selector(runSynchronousNSURLConnections) withObject:nil waitUntilDone:YES];
  77 +}
  78 +
  79 +
  80 +- (void)runSynchronousNSURLConnections
  81 +{
  82 + int runTimes = 10;
  83 + NSTimeInterval times[runTimes];
  84 + int i;
  85 + for (i=0; i<runTimes; i++) {
  86 + NSDate *startTime = [NSDate date];
  87 +
  88 + NSURLResponse *response = nil;
  89 + NSError *error = nil;
  90 + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:testURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
  91 + [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
  92 + if (error) {
  93 + NSLog(@"Request failed - cannot proceed with test");
  94 + return;
  95 + }
  96 + times[i] = [[NSDate date] timeIntervalSinceDate:startTime];
  97 + }
  98 + NSTimeInterval bestTime = 1000;
  99 + NSTimeInterval worstTime = 0;
  100 + NSTimeInterval totalTime = 0;
  101 + for (i=0; i<runTimes; i++) {
  102 + if (times[i] < bestTime) {
  103 + bestTime = times[i];
  104 + }
  105 + if (times[i] > worstTime) {
  106 + worstTime = times[i];
  107 + }
  108 + totalTime += times[i];
  109 + }
  110 + NSLog(@"Ran runTimes requests in %f seconds (average time: %f secs / best time: %f secs / worst time: %f secs)",totalTime,totalTime/runTimes,bestTime,worstTime);
  111 +}
  112 +
  113 +
24 - (void)testASIHTTPRequestAsyncPerformance 114 - (void)testASIHTTPRequestAsyncPerformance
25 { 115 {
26 [self performSelectorOnMainThread:@selector(startASIHTTPRequests) withObject:nil waitUntilDone:NO]; 116 [self performSelectorOnMainThread:@selector(startASIHTTPRequests) withObject:nil waitUntilDone:NO];
27 } 117 }
28 118
  119 +- (void)testASIHTTPRequestAsyncPerformanceWithQueue
  120 +{
  121 + [self performSelectorOnMainThread:@selector(startASIHTTPRequestsWithQueue) withObject:nil waitUntilDone:NO];
  122 +}
  123 +
29 124
30 - (void)startASIHTTPRequests 125 - (void)startASIHTTPRequests
31 { 126 {
@@ -35,11 +130,33 @@ @@ -35,11 +130,33 @@
35 int i; 130 int i;
36 for (i=0; i<10; i++) { 131 for (i=0; i<10; i++) {
37 ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]]; 132 ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]];
  133 + //Send the same headers as NSURLRequest
  134 + [request addRequestHeader:@"Pragma" value:@"no-cache"];
  135 + [request addRequestHeader:@"Accept" value:@"*/*"];
  136 + [request addRequestHeader:@"Accept-Language" value:@"en/us"];
38 [request setDelegate:self]; 137 [request setDelegate:self];
39 [request start]; 138 [request start];
40 } 139 }
41 } 140 }
42 141
  142 +- (void)startASIHTTPRequestsWithQueue
  143 +{
  144 + bytesDownloaded = 0;
  145 + [self setRequestsComplete:0];
  146 + [self setTestStartDate:[NSDate date]];
  147 + int i;
  148 + NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
  149 + for (i=0; i<10; i++) {
  150 + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]];
  151 + //Send the same headers as NSURLRequest
  152 + [request addRequestHeader:@"Pragma" value:@"no-cache"];
  153 + [request addRequestHeader:@"Accept" value:@"*/*"];
  154 + [request addRequestHeader:@"Accept-Language" value:@"en/us"];
  155 + [request setDelegate:self];
  156 + [queue addOperation:request];
  157 + }
  158 +}
  159 +
43 - (void)requestFailed:(ASIHTTPRequest *)request 160 - (void)requestFailed:(ASIHTTPRequest *)request
44 { 161 {
45 GHFail(@"Cannot proceed with ASIHTTPRequest test - a request failed"); 162 GHFail(@"Cannot proceed with ASIHTTPRequest test - a request failed");
@@ -99,6 +216,7 @@ @@ -99,6 +216,7 @@
99 } 216 }
100 } 217 }
101 218
  219 +@synthesize testURL;
102 @synthesize requestsComplete; 220 @synthesize requestsComplete;
103 @synthesize testStartDate; 221 @synthesize testStartDate;
104 @synthesize responseData; 222 @synthesize responseData;