Ben Copsey

Added support for persistent http connections

More performance tests
... ... @@ -331,6 +331,14 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
// The number of times this request has retried (when numberOfTimesToRetryOnTimeout > 0)
int retryCount;
// 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
// Persistent connections only work when the server sends a 'Keep-Alive' header
// Default is YES
BOOL shouldAttemptPersistentConnection;
// Set to yes when an appropriate keep-alive header is found
BOOL canUsePersistentConnection;
}
#pragma mark init / dealloc
... ... @@ -666,4 +674,5 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
@property (assign) BOOL shouldRunInBackgroundThread;
@property (assign) int numberOfTimesToRetryOnTimeout;
@property (assign, readonly) int retryCount;
@property (assign) BOOL shouldAttemptPersistentConnection;
@end
... ...
... ... @@ -21,7 +21,7 @@
#import "ASIInputStream.h"
// Automatically set on build
NSString *ASIHTTPRequestVersion = @"v1.2-52 2009-12-19";
NSString *ASIHTTPRequestVersion = @"v1.2-53 2009-12-19";
NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
... ... @@ -154,6 +154,7 @@ static BOOL isiPhoneOS2;
@property (assign) BOOL isSynchronous;
@property (assign) BOOL inProgress;
@property (assign) int retryCount;
@property (assign) BOOL canUsePersistentConnection;
@end
... ... @@ -191,6 +192,7 @@ static BOOL isiPhoneOS2;
self = [super init];
[self setRequestMethod:@"GET"];
[self setShouldAttemptPersistentConnection:YES];
[self setShouldPresentCredentialsBeforeChallenge:YES];
[self setShouldRedirect:YES];
[self setShowAccurateProgress:YES];
... ... @@ -808,6 +810,9 @@ static BOOL isiPhoneOS2;
CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, [NSMutableDictionary dictionaryWithObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]);
}
// Use a persistent connection if possible
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
// Handle proxy settings
... ... @@ -930,10 +935,20 @@ static BOOL isiPhoneOS2;
[self scheduleReadStream];
}
while (!complete) {
[self checkRequestStatus];
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, NO);
}
// if ([NSThread isMainThread]) {
// [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES];
// CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeOutSeconds, NO);
//
// } else if (!uploadProgressDelegate && !downloadProgressDelegate) {
// CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeOutSeconds, NO);
// [self checkRequestStatus];
// } else {
while (!complete) {
[self checkRequestStatus];
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, NO);
}
//}
}
// 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;
}
// Handle connection persistence
if ([self shouldAttemptPersistentConnection] && [[[self responseHeaders] objectForKey:@"Connection"] isEqualToString:@"Keep-Alive"]) {
NSString *keepAliveHeader = [[self responseHeaders] objectForKey:@"Keep-Alive"];
if (keepAliveHeader) {
int timeout = 0;
int max = 0;
NSScanner *scanner = [NSScanner scannerWithString:keepAliveHeader];
[scanner scanString:@"timeout=" intoString:NULL];
[scanner scanInt:&timeout];
[scanner scanUpToString:@"max=" intoString:NULL];
[scanner scanString:@"max=" intoString:NULL];
[scanner scanInt:&max];
if (max > 5) {
[self setCanUsePersistentConnection:YES];
CFRetain(readStream);
[NSTimer scheduledTimerWithTimeInterval:max target:[self class] selector:@selector(closePersistentConnection:) userInfo:(id)readStream repeats:NO];
}
}
}
}
CFRelease(headers);
return isAuthenticationChallenge;
}
#pragma mark http authentication
... ... @@ -2222,12 +2258,14 @@ static BOOL isiPhoneOS2;
if ([self needsRedirect]) {
return;
}
long long bufferSize = 2048;
if (contentLength > 262144) {
bufferSize = 65536;
} else if (contentLength > 65536) {
bufferSize = 16384;
}
// long long bufferSize = 2048;
// if (contentLength > 262144) {
// bufferSize = 65536;
// } else if (contentLength > 65536) {
// bufferSize = 16384;
// }
long long bufferSize = 262144;
// Reduce the buffer size if we're receiving data too quickly when bandwidth throttling is active
// This just augments the throttling done in measureBandwidthUsage to reduce the amount we go over the limit
... ... @@ -2397,7 +2435,9 @@ static BOOL isiPhoneOS2;
if (readStreamIsScheduled) {
CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
CFReadStreamClose(readStream);
if (!canUsePersistentConnection) {
CFReadStreamClose(readStream);
}
CFRelease(readStream);
readStream = NULL;
}
... ... @@ -2421,6 +2461,13 @@ static BOOL isiPhoneOS2;
}
}
+ (void)closePersistentConnection:(NSTimer *)timer
{
CFReadStreamRef stream = (CFReadStreamRef)[timer userInfo];
CFReadStreamClose(stream);
CFRelease(stream);
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone
... ... @@ -3427,6 +3474,8 @@ static BOOL isiPhoneOS2;
@synthesize shouldRunInBackgroundThread;
@synthesize numberOfTimesToRetryOnTimeout;
@synthesize retryCount;
@synthesize shouldAttemptPersistentConnection;
@synthesize canUsePersistentConnection;
@end
... ...
... ... @@ -10,6 +10,8 @@
#import "ASITestCase.h"
@interface PerformanceTests : ASITestCase {
NSURL *testURL;
NSDate *testStartDate;
int requestsComplete;
NSMutableArray *responseData;
... ... @@ -19,6 +21,7 @@
- (void)testASIHTTPRequestAsyncPerformance;
- (void)testNSURLConnectionAsyncPerformance;
@property (retain,nonatomic) NSURL *testURL;
@property (retain,nonatomic) NSDate *testStartDate;
@property (assign,nonatomic) int requestsComplete;
@property (retain,nonatomic) NSMutableArray *responseData;
... ...
... ... @@ -9,6 +9,7 @@
#import "PerformanceTests.h"
#import "ASIHTTPRequest.h"
@interface NSURLConnectionSubclass : NSURLConnection {
int tag;
}
... ... @@ -21,11 +22,105 @@
@implementation PerformanceTests
- (void)setUp
{
[self setTestURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
}
- (void)testASIHTTPRequestSynchronousPerformance
{
[self performSelectorOnMainThread:@selector(runSynchronousASIHTTPRequests) withObject:nil waitUntilDone:YES];
}
- (void)runSynchronousASIHTTPRequests
{
int runTimes = 10;
NSTimeInterval times[runTimes];
int i;
for (i=0; i<runTimes; i++) {
NSDate *startTime = [NSDate date];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:testURL];
//Send the same headers as NSURLRequest
[request addRequestHeader:@"Pragma" value:@"no-cache"];
[request addRequestHeader:@"Accept" value:@"*/*"];
[request addRequestHeader:@"Accept-Language" value:@"en/us"];
[request setUseCookiePersistance:NO];
[request setUseSessionPersistance:NO];
//[request setShouldRunInBackgroundThread:YES];
[request startSynchronous];
if ([request error]) {
NSLog(@"Request failed - cannot proceed with test");
return;
}
times[i] = [[NSDate date] timeIntervalSinceDate:startTime];
}
NSTimeInterval bestTime = 1000;
NSTimeInterval worstTime = 0;
NSTimeInterval totalTime = 0;
for (i=0; i<runTimes; i++) {
if (times[i] < bestTime) {
bestTime = times[i];
}
if (times[i] > worstTime) {
worstTime = times[i];
}
totalTime += times[i];
}
NSLog(@"Ran runTimes requests in %f seconds (average time: %f secs / best time: %f secs / worst time: %f secs)",totalTime,totalTime/runTimes,bestTime,worstTime);
}
- (void)testNSURLConnectionSynchronousPerformance
{
[self performSelectorOnMainThread:@selector(runSynchronousNSURLConnections) withObject:nil waitUntilDone:YES];
}
- (void)runSynchronousNSURLConnections
{
int runTimes = 10;
NSTimeInterval times[runTimes];
int i;
for (i=0; i<runTimes; i++) {
NSDate *startTime = [NSDate date];
NSURLResponse *response = nil;
NSError *error = nil;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:testURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error) {
NSLog(@"Request failed - cannot proceed with test");
return;
}
times[i] = [[NSDate date] timeIntervalSinceDate:startTime];
}
NSTimeInterval bestTime = 1000;
NSTimeInterval worstTime = 0;
NSTimeInterval totalTime = 0;
for (i=0; i<runTimes; i++) {
if (times[i] < bestTime) {
bestTime = times[i];
}
if (times[i] > worstTime) {
worstTime = times[i];
}
totalTime += times[i];
}
NSLog(@"Ran runTimes requests in %f seconds (average time: %f secs / best time: %f secs / worst time: %f secs)",totalTime,totalTime/runTimes,bestTime,worstTime);
}
- (void)testASIHTTPRequestAsyncPerformance
{
[self performSelectorOnMainThread:@selector(startASIHTTPRequests) withObject:nil waitUntilDone:NO];
}
- (void)testASIHTTPRequestAsyncPerformanceWithQueue
{
[self performSelectorOnMainThread:@selector(startASIHTTPRequestsWithQueue) withObject:nil waitUntilDone:NO];
}
- (void)startASIHTTPRequests
{
... ... @@ -35,11 +130,33 @@
int i;
for (i=0; i<10; i++) {
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]];
//Send the same headers as NSURLRequest
[request addRequestHeader:@"Pragma" value:@"no-cache"];
[request addRequestHeader:@"Accept" value:@"*/*"];
[request addRequestHeader:@"Accept-Language" value:@"en/us"];
[request setDelegate:self];
[request start];
}
}
- (void)startASIHTTPRequestsWithQueue
{
bytesDownloaded = 0;
[self setRequestsComplete:0];
[self setTestStartDate:[NSDate date]];
int i;
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
for (i=0; i<10; i++) {
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]];
//Send the same headers as NSURLRequest
[request addRequestHeader:@"Pragma" value:@"no-cache"];
[request addRequestHeader:@"Accept" value:@"*/*"];
[request addRequestHeader:@"Accept-Language" value:@"en/us"];
[request setDelegate:self];
[queue addOperation:request];
}
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
GHFail(@"Cannot proceed with ASIHTTPRequest test - a request failed");
... ... @@ -99,6 +216,7 @@
}
}
@synthesize testURL;
@synthesize requestsComplete;
@synthesize testStartDate;
@synthesize responseData;
... ...