Ben Copsey

Tweak persistent connection behaviour again to avoid persistent connections only…

… when they are explicitly turned off, or when we've got http1.0 and no connection:keep-alive header
canUsePersistentConnection -> connectionCanBeReused, and add public accessor
Add test for connection: close
@@ -344,7 +344,7 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; @@ -344,7 +344,7 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
344 BOOL shouldAttemptPersistentConnection; 344 BOOL shouldAttemptPersistentConnection;
345 345
346 // Set to yes when an appropriate keep-alive header is found 346 // Set to yes when an appropriate keep-alive header is found
347 - BOOL canUsePersistentConnection; 347 + BOOL connectionCanBeReused;
348 348
349 // Populated with the number of seconds the server told us we could keep a persistent connection around 349 // Populated with the number of seconds the server told us we could keep a persistent connection around
350 // A future date is created from this and used for expiring the connection, this is stored in connectionInfo's expires value 350 // A future date is created from this and used for expiring the connection, this is stored in connectionInfo's expires value
@@ -717,4 +717,5 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; @@ -717,4 +717,5 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
717 @property (assign, readonly) int retryCount; 717 @property (assign, readonly) int retryCount;
718 @property (assign) BOOL shouldAttemptPersistentConnection; 718 @property (assign) BOOL shouldAttemptPersistentConnection;
719 @property (assign) BOOL shouldUseRFC2616RedirectBehaviour; 719 @property (assign) BOOL shouldUseRFC2616RedirectBehaviour;
  720 +@property (assign, readonly) BOOL connectionCanBeReused;
720 @end 721 @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.5-47 2010-02-24"; 24 +NSString *ASIHTTPRequestVersion = @"v1.5-48 2010-02-24";
25 25
26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; 26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
27 27
@@ -175,7 +175,7 @@ static BOOL isiPhoneOS2; @@ -175,7 +175,7 @@ static BOOL isiPhoneOS2;
175 @property (assign) BOOL isSynchronous; 175 @property (assign) BOOL isSynchronous;
176 @property (assign) BOOL inProgress; 176 @property (assign) BOOL inProgress;
177 @property (assign) int retryCount; 177 @property (assign) int retryCount;
178 -@property (assign) BOOL canUsePersistentConnection; 178 +@property (assign) BOOL connectionCanBeReused;
179 @property (retain, nonatomic) NSMutableDictionary *connectionInfo; 179 @property (retain, nonatomic) NSMutableDictionary *connectionInfo;
180 @property (retain, nonatomic) NSInputStream *readStream; 180 @property (retain, nonatomic) NSInputStream *readStream;
181 @property (assign) ASIAuthenticationState authenticationNeeded; 181 @property (assign) ASIAuthenticationState authenticationNeeded;
@@ -1007,7 +1007,7 @@ static BOOL isiPhoneOS2; @@ -1007,7 +1007,7 @@ static BOOL isiPhoneOS2;
1007 1007
1008 1008
1009 if (!streamSuccessfullyOpened) { 1009 if (!streamSuccessfullyOpened) {
1010 - [self setCanUsePersistentConnection:NO]; 1010 + [self setConnectionCanBeReused:NO];
1011 [self destroyReadStream]; 1011 [self destroyReadStream];
1012 [[self cancelledLock] unlock]; 1012 [[self cancelledLock] unlock];
1013 [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]]; 1013 [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]];
@@ -1579,7 +1579,7 @@ static BOOL isiPhoneOS2; @@ -1579,7 +1579,7 @@ static BOOL isiPhoneOS2;
1579 [connectionsLock unlock]; 1579 [connectionsLock unlock];
1580 [self destroyReadStream]; 1580 [self destroyReadStream];
1581 } 1581 }
1582 - if ([self canUsePersistentConnection]) { 1582 + if ([self connectionCanBeReused]) {
1583 [[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:closeStreamTime] forKey:@"expires"]; 1583 [[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:closeStreamTime] forKey:@"expires"];
1584 } 1584 }
1585 1585
@@ -1773,35 +1773,39 @@ static BOOL isiPhoneOS2; @@ -1773,35 +1773,39 @@ static BOOL isiPhoneOS2;
1773 } 1773 }
1774 1774
1775 // Handle connection persistence 1775 // Handle connection persistence
1776 - NSString *httpVersion = [(NSString *)CFHTTPMessageCopyVersion(message) autorelease]; 1776 + if ([self shouldAttemptPersistentConnection]) {
1777 - 1777 +
1778 - // We won't re-use this connection if this request is set to use HTTP 1.0, or the server is talking in HTTP 1.0, or persistent connections are turned off for this request 1778 + NSString *connectionHeader = [[[self responseHeaders] objectForKey:@"Connection"] lowercaseString];
1779 - if (![self useHTTPVersionOne] && [self shouldAttemptPersistentConnection] && ![httpVersion isEqualToString:(NSString *)kCFHTTPVersion1_0]) { 1779 + NSString *httpVersion = [(NSString *)CFHTTPMessageCopyVersion(message) autorelease];
  1780 +
  1781 + // Don't re-use the connection if the server is HTTP 1.0 and didn't send Connection: Keep-Alive
  1782 + if (![httpVersion isEqualToString:(NSString *)kCFHTTPVersion1_0] || [connectionHeader isEqualToString:@"keep-alive"]) {
1780 1783
1781 - // See if server explicitly told us to close the connection 1784 + // See if server explicitly told us to close the connection
1782 - if (![[[[self responseHeaders] objectForKey:@"Connection"] lowercaseString] isEqualToString:@"close"]) { 1785 + if (![connectionHeader isEqualToString:@"close"]) {
1783 - 1786 +
1784 - NSString *keepAliveHeader = [[self responseHeaders] objectForKey:@"Keep-Alive"]; 1787 + NSString *keepAliveHeader = [[self responseHeaders] objectForKey:@"Keep-Alive"];
1785 - 1788 +
1786 - // If we got a keep alive header, we'll reuse the connection for as long as the server tells us 1789 + // If we got a keep alive header, we'll reuse the connection for as long as the server tells us
1787 - if (keepAliveHeader) { 1790 + if (keepAliveHeader) {
1788 - int timeout = 0; 1791 + int timeout = 0;
1789 - int max = 0; 1792 + int max = 0;
1790 - NSScanner *scanner = [NSScanner scannerWithString:keepAliveHeader]; 1793 + NSScanner *scanner = [NSScanner scannerWithString:keepAliveHeader];
1791 - [scanner scanString:@"timeout=" intoString:NULL]; 1794 + [scanner scanString:@"timeout=" intoString:NULL];
1792 - [scanner scanInt:&timeout]; 1795 + [scanner scanInt:&timeout];
1793 - [scanner scanUpToString:@"max=" intoString:NULL]; 1796 + [scanner scanUpToString:@"max=" intoString:NULL];
1794 - [scanner scanString:@"max=" intoString:NULL]; 1797 + [scanner scanString:@"max=" intoString:NULL];
1795 - [scanner scanInt:&max]; 1798 + [scanner scanInt:&max];
1796 - if (max > 5) { 1799 + if (max > 5) {
1797 - [self setCanUsePersistentConnection:YES]; 1800 + [self setConnectionCanBeReused:YES];
1798 - closeStreamTime = timeout; 1801 + closeStreamTime = timeout;
  1802 + }
  1803 +
  1804 + // Otherwise, we'll assume we can keep this connection open
  1805 + } else {
  1806 + [self setConnectionCanBeReused:YES];
  1807 + closeStreamTime = 60;
1799 } 1808 }
1800 -  
1801 - // Otherwise, we'll assume we can keep this connection open  
1802 - } else {  
1803 - [self setCanUsePersistentConnection:YES];  
1804 - closeStreamTime = 60;  
1805 } 1809 }
1806 } 1810 }
1807 } 1811 }
@@ -2582,7 +2586,7 @@ static BOOL isiPhoneOS2; @@ -2582,7 +2586,7 @@ static BOOL isiPhoneOS2;
2582 2586
2583 2587
2584 [connectionsLock lock]; 2588 [connectionsLock lock];
2585 - if (![self canUsePersistentConnection]) { 2589 + if (![self connectionCanBeReused]) {
2586 [self unscheduleReadStream]; 2590 [self unscheduleReadStream];
2587 } 2591 }
2588 #if DEBUG_PERSISTENT_CONNECTIONS 2592 #if DEBUG_PERSISTENT_CONNECTIONS
@@ -2653,7 +2657,7 @@ static BOOL isiPhoneOS2; @@ -2653,7 +2657,7 @@ static BOOL isiPhoneOS2;
2653 CFReadStreamSetClient((CFReadStreamRef)[self readStream], kCFStreamEventNone, NULL, NULL); 2657 CFReadStreamSetClient((CFReadStreamRef)[self readStream], kCFStreamEventNone, NULL, NULL);
2654 [connectionsLock lock]; 2658 [connectionsLock lock];
2655 2659
2656 - if (![self canUsePersistentConnection]) { 2660 + if (![self connectionCanBeReused]) {
2657 CFReadStreamUnscheduleFromRunLoop((CFReadStreamRef)[self readStream], CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 2661 CFReadStreamUnscheduleFromRunLoop((CFReadStreamRef)[self readStream], CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
2658 CFReadStreamClose((CFReadStreamRef)[self readStream]); 2662 CFReadStreamClose((CFReadStreamRef)[self readStream]);
2659 [self setReadStreamIsScheduled:NO]; 2663 [self setReadStreamIsScheduled:NO];
@@ -3734,7 +3738,7 @@ static BOOL isiPhoneOS2; @@ -3734,7 +3738,7 @@ static BOOL isiPhoneOS2;
3734 @synthesize numberOfTimesToRetryOnTimeout; 3738 @synthesize numberOfTimesToRetryOnTimeout;
3735 @synthesize retryCount; 3739 @synthesize retryCount;
3736 @synthesize shouldAttemptPersistentConnection; 3740 @synthesize shouldAttemptPersistentConnection;
3737 -@synthesize canUsePersistentConnection; 3741 +@synthesize connectionCanBeReused;
3738 @synthesize connectionInfo; 3742 @synthesize connectionInfo;
3739 @synthesize readStream; 3743 @synthesize readStream;
3740 @synthesize readStreamIsScheduled; 3744 @synthesize readStreamIsScheduled;
@@ -60,4 +60,5 @@ @@ -60,4 +60,5 @@
60 - (void)testReachability; 60 - (void)testReachability;
61 #endif 61 #endif
62 - (void)testAutomaticRetry; 62 - (void)testAutomaticRetry;
  63 +- (void)testCloseConnection;
63 @end 64 @end
@@ -1345,6 +1345,16 @@ @@ -1345,6 +1345,16 @@
1345 1345
1346 } 1346 }
1347 1347
  1348 +- (void)testCloseConnection
  1349 +{
  1350 + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/close-connection"]];
  1351 + [request startSynchronous];
  1352 +
  1353 + BOOL success = ![request connectionCanBeReused];
  1354 + GHAssertTrue(success,@"Should not be able to re-use a request sent with Connection:close");
  1355 +
  1356 +}
  1357 +
1348 1358
1349 // Will be called on Mac OS 1359 // Will be called on Mac OS
1350 - (void)setDoubleValue:(double)newProgress; 1360 - (void)setDoubleValue:(double)newProgress;