try to re-establish a dropped connection if using keepalive

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@20128 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Richard Frith-Macdonald 2004-09-24 19:21:57 +00:00
parent c11628ed31
commit 2a49f09265
2 changed files with 245 additions and 191 deletions

View file

@ -2,6 +2,8 @@
* Source/GSArray.m: Make perform methods enumerate forwards.
* Source/Additions/GSXML.m: ([nodeAtIndex:]) fix memory leak.
* Source/GSHTTPURLHandle.m: Re-instate change to try to provide
better support for connections kept alive.
2004-09-22 Richard Frith-Macdonald <rfm@gnu.org>

View file

@ -57,8 +57,10 @@ static NSString *httpVersion = @"1.1";
{
BOOL tunnel;
BOOL debug;
BOOL keepalive;
NSFileHandle *sock;
NSURL *url;
NSURL *u;
NSMutableData *dat;
GSMimeParser *parser;
GSMimeDocument *document;
@ -67,6 +69,7 @@ static NSString *httpVersion = @"1.1";
NSData *wData;
NSMutableDictionary *request;
unsigned int bodyPos;
unsigned int redirects;
enum {
idle,
connecting,
@ -75,6 +78,7 @@ static NSString *httpVersion = @"1.1";
} connectionState;
}
- (void) setDebug: (BOOL)flag;
- (void) _tryLoadInBackground: (NSURL*)fromURL;
@end
/**
@ -251,6 +255,7 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
- (void) dealloc
{
RELEASE(sock);
RELEASE(u);
RELEASE(url);
RELEASE(dat);
RELEASE(parser);
@ -306,10 +311,12 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
NSMutableData *buf;
NSString *version;
if (debug == YES) NSLog(@"%@", NSStringFromSelector(_cmd));
s = [basic mutableCopy];
if ([[url query] length] > 0)
if ([[u query] length] > 0)
{
[s appendFormat: @"?%@", [url query]];
[s appendFormat: @"?%@", [u query]];
}
version = [request objectForKey: NSHTTPPropertyServerHTTPVersionKey];
@ -321,7 +328,7 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
if ([wProperties objectForKey: @"host"] == nil)
{
[wProperties setObject: [url host] forKey: @"host"];
[wProperties setObject: [u host] forKey: @"host"];
}
if ([wData length] > 0)
@ -339,18 +346,18 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
}
if ([wProperties objectForKey: @"authorisation"] == nil)
{
if ([url user] != nil)
if ([u user] != nil)
{
NSString *auth;
if ([[url password] length] > 0)
if ([[u password] length] > 0)
{
auth = [NSString stringWithFormat: @"%@:%@",
[url user], [url password]];
[u user], [u password]];
}
else
{
auth = [NSString stringWithFormat: @"%@", [url user]];
auth = [NSString stringWithFormat: @"%@", [u user]];
}
auth = [NSString stringWithFormat: @"Basic %@",
[GSMimeDocument encodeBase64String: auth]];
@ -374,17 +381,8 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
if (wData != nil)
{
[buf appendData: wData];
DESTROY(wData);
}
/*
* Send request to server.
*/
[sock writeInBackgroundAndNotify: buf];
if (debug == YES) debugWrite(self, buf);
RELEASE(buf);
RELEASE(s);
/*
* Watch for write completion.
*/
@ -393,6 +391,14 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
name: GSFileHandleWriteCompletionNotification
object: sock];
connectionState = writing;
/*
* Send request to server.
*/
if (debug == YES) debugWrite(self, buf);
[sock writeInBackgroundAndNotify: buf];
RELEASE(buf);
RELEASE(s);
}
- (void) bgdRead: (NSNotification*) not
@ -490,6 +496,7 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
d = [parser data];
r = NSMakeRange(bodyPos, [d length] - bodyPos);
bodyPos = 0;
DESTROY(wData);
[self didLoadBytes: [d subdataWithRange: r]
loadComplete: YES];
}
@ -557,172 +564,12 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
- (void) loadInBackground
{
NSNotificationCenter *nc;
NSString *host = nil;
NSString *port = nil;
NSString *s;
/*
* Don't start a load if one is in progress.
*/
if (connectionState != idle)
{
NSLog(@"Attempt to load an http handle which is not idle ... ignored");
return;
}
[dat setLength: 0];
RELEASE(document);
RELEASE(parser);
parser = [GSMimeParser new];
document = RETAIN([parser mimeDocument]);
[self beginLoadInBackground];
host = [url host];
port = (id)[url port];
if (port != nil)
{
port = [NSString stringWithFormat: @"%u", [port intValue]];
}
else
{
port = [url scheme];
}
if ([port isEqualToString: @"https"])
{
port = @"443";
}
else if ([port isEqualToString: @"http"])
{
port = @"80";
}
if (sock != nil)
{
NSString *method;
NSString *path;
NSString *basic;
method = [request objectForKey: GSHTTPPropertyMethodKey];
if (method == nil)
{
if ([wData length] > 0)
{
method = @"POST";
}
else
{
method = @"GET";
}
}
path = [[url path] stringByTrimmingSpaces];
if ([path length] == 0)
{
path = @"/";
}
basic = [NSString stringWithFormat: @"%@ %@", method, path];
[self bgdApply: basic];
return;
}
/*
* If we have a local address specified, tell the file handle to bind to it.
*/
s = [request objectForKey: GSHTTPPropertyLocalHostKey];
if ([s length] > 0)
{
s = [NSString stringWithFormat: @"bind-%@", s];
}
else
{
s = @"tcp"; // Bind to any.
}
if ([[request objectForKey: GSHTTPPropertyProxyHostKey] length] == 0)
{
if ([[url scheme] isEqualToString: @"https"])
{
NSString *cert;
if (sslClass == 0)
{
[self backgroundLoadDidFailWithReason:
@"https not supported ... needs SSL bundle"];
return;
}
sock = [sslClass fileHandleAsClientInBackgroundAtAddress: host
service: port
protocol: s];
cert = [request objectForKey: GSHTTPPropertyCertificateFileKey];
if ([cert length] > 0)
{
NSString *key;
NSString *pwd;
key = [request objectForKey: GSHTTPPropertyKeyFileKey];
pwd = [request objectForKey: GSHTTPPropertyPasswordKey];
[sock sslSetCertificate: cert privateKey: key PEMpasswd: pwd];
}
}
else
{
sock = [NSFileHandle fileHandleAsClientInBackgroundAtAddress: host
service: port
protocol: s];
}
}
else
{
if ([[request objectForKey: GSHTTPPropertyProxyPortKey] length] == 0)
{
[request setObject: @"8080" forKey: GSHTTPPropertyProxyPortKey];
}
if ([[url scheme] isEqualToString: @"https"])
{
if (sslClass == 0)
{
[self backgroundLoadDidFailWithReason:
@"https not supported ... needs SSL bundle"];
return;
}
host = [request objectForKey: GSHTTPPropertyProxyHostKey];
port = [request objectForKey: GSHTTPPropertyProxyPortKey];
sock = [sslClass fileHandleAsClientInBackgroundAtAddress: host
service: port
protocol: s];
}
else
{
host = [request objectForKey: GSHTTPPropertyProxyHostKey];
port = [request objectForKey: GSHTTPPropertyProxyPortKey];
sock = [NSFileHandle fileHandleAsClientInBackgroundAtAddress: host
service: port
protocol: s];
}
}
if (sock == nil)
{
extern int errno;
/*
* Tell superclass that the load failed - let it do housekeeping.
*/
[self backgroundLoadDidFailWithReason: [NSString stringWithFormat:
@"Unable to connect to %@:%@ ... %s",
host, port, GSLastErrorStr(errno)]];
return;
}
RETAIN(sock);
nc = [NSNotificationCenter defaultCenter];
[nc addObserver: self
selector: @selector(bgdConnect:)
name: GSFileHandleConnectCompletionNotification
object: sock];
connectionState = connecting;
[self _tryLoadInBackground: nil];
}
- (void) endLoadInBackground
{
DESTROY(wData);
if (connectionState != idle)
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
@ -753,7 +600,9 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
NSString *method;
NSString *path;
path = [[url path] stringByTrimmingSpaces];
if (debug == YES) NSLog(@"%@", NSStringFromSelector(_cmd));
path = [[u path] stringByTrimmingSpaces];
if ([path length] == 0)
{
path = @"/";
@ -786,7 +635,7 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
/*
* If SSL via proxy, set up tunnel first
*/
if ([[url scheme] isEqualToString: @"https"]
if ([[u scheme] isEqualToString: @"https"]
&& [[request objectForKey: GSHTTPPropertyProxyHostKey] length] > 0)
{
NSRunLoop *loop = [NSRunLoop currentRunLoop];
@ -803,15 +652,15 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
{
version = httpVersion;
}
if ([url port] == nil)
if ([u port] == nil)
{
cmd = [NSString stringWithFormat: @"CONNECT %@:443 HTTP/%@\r\n\r\n",
[url host], version];
[u host], version];
}
else
{
cmd = [NSString stringWithFormat: @"CONNECT %@:%@ HTTP/%@\r\n\r\n",
[url host], [url port], version];
[u host], [u port], version];
}
/*
@ -829,8 +678,8 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
object: sock];
buf = [cmd dataUsingEncoding: NSASCIIStringEncoding];
[sock writeInBackgroundAndNotify: buf];
if (debug == YES) debugWrite(self, buf);
[sock writeInBackgroundAndNotify: buf];
when = [NSDate alloc];
while (tunnel == YES)
@ -855,7 +704,7 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
return;
}
}
if ([[url scheme] isEqualToString: @"https"])
if ([[u scheme] isEqualToString: @"https"])
{
/*
* If we are an https connection, negotiate secure connection
@ -885,17 +734,17 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
}
}
if ([[request objectForKey: GSHTTPPropertyProxyHostKey] length] > 0
&& [[url scheme] isEqualToString: @"https"] == NO)
&& [[u scheme] isEqualToString: @"https"] == NO)
{
if ([url port] == nil)
if ([u port] == nil)
{
s = [[NSMutableString alloc] initWithFormat: @"%@ http://%@%@",
method, [url host], path];
method, [u host], path];
}
else
{
s = [[NSMutableString alloc] initWithFormat: @"%@ http://%@:%@%@",
method, [url host], [url port], path];
method, [u host], [u port], path];
}
}
else // no proxy
@ -910,13 +759,32 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
- (void) bgdWrite: (NSNotification*)notification
{
NSNotificationCenter *nc;
NSDictionary *userInfo = [notification userInfo];
NSString *e;
if (debug == YES) NSLog(@"%@", NSStringFromSelector(_cmd));
e = [userInfo objectForKey: GSFileHandleNotificationError];
if (e != nil)
{
tunnel = NO;
if (keepalive == YES)
{
/*
* The write failed ... connection dropped ... and we
* are re-using an existing connection (keepalive = YES)
* then we may try again with a new connection.
*/
nc = [NSNotificationCenter defaultCenter];
[nc removeObserver: self
name: GSFileHandleWriteCompletionNotification
object: sock];
[sock closeFile];
DESTROY(sock);
connectionState = idle;
[self _tryLoadInBackground: u];
return;
}
NSLog(@"Failed to write command to socket - %@", e);
/*
* Tell superclass that the load failed - let it do housekeeping.
@ -927,8 +795,6 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
}
else
{
NSNotificationCenter *nc;
/*
* Don't watch for write completions any more.
*/
@ -1030,6 +896,192 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
debug = flag;
}
- (void) _tryLoadInBackground: (NSURL*)fromURL
{
NSNotificationCenter *nc;
NSString *host = nil;
NSString *port = nil;
NSString *s;
/*
* Don't start a load if one is in progress.
*/
if (connectionState != idle)
{
NSLog(@"Attempt to load an http handle which is not idle ... ignored");
return;
}
[dat setLength: 0];
RELEASE(document);
RELEASE(parser);
parser = [GSMimeParser new];
document = RETAIN([parser mimeDocument]);
/*
* First time round, fromURL is nil, so we use the url ivar and
* we notify that the load is begining. On retries we get a real
* value in fromURL to use.
*/
if (fromURL == nil)
{
redirects = 0;
ASSIGN(u, url);
[self beginLoadInBackground];
}
else
{
ASSIGN(u, fromURL);
}
host = [u host];
port = (id)[u port];
if (port != nil)
{
port = [NSString stringWithFormat: @"%u", [port intValue]];
}
else
{
port = [u scheme];
}
if ([port isEqualToString: @"https"])
{
port = @"443";
}
else if ([port isEqualToString: @"http"])
{
port = @"80";
}
if (sock == nil)
{
keepalive = NO; // New connection
/*
* If we have a local address specified,
* tell the file handle to bind to it.
*/
s = [request objectForKey: GSHTTPPropertyLocalHostKey];
if ([s length] > 0)
{
s = [NSString stringWithFormat: @"bind-%@", s];
}
else
{
s = @"tcp"; // Bind to any.
}
if ([[request objectForKey: GSHTTPPropertyProxyHostKey] length] == 0)
{
if ([[u scheme] isEqualToString: @"https"])
{
NSString *cert;
if (sslClass == 0)
{
[self backgroundLoadDidFailWithReason:
@"https not supported ... needs SSL bundle"];
return;
}
sock = [sslClass fileHandleAsClientInBackgroundAtAddress: host
service: port
protocol: s];
cert = [request objectForKey: GSHTTPPropertyCertificateFileKey];
if ([cert length] > 0)
{
NSString *key;
NSString *pwd;
key = [request objectForKey: GSHTTPPropertyKeyFileKey];
pwd = [request objectForKey: GSHTTPPropertyPasswordKey];
[sock sslSetCertificate: cert privateKey: key PEMpasswd: pwd];
}
}
else
{
sock = [NSFileHandle fileHandleAsClientInBackgroundAtAddress: host
service: port
protocol: s];
}
}
else
{
if ([[request objectForKey: GSHTTPPropertyProxyPortKey] length] == 0)
{
[request setObject: @"8080" forKey: GSHTTPPropertyProxyPortKey];
}
if ([[u scheme] isEqualToString: @"https"])
{
if (sslClass == 0)
{
[self backgroundLoadDidFailWithReason:
@"https not supported ... needs SSL bundle"];
return;
}
host = [request objectForKey: GSHTTPPropertyProxyHostKey];
port = [request objectForKey: GSHTTPPropertyProxyPortKey];
sock = [sslClass fileHandleAsClientInBackgroundAtAddress: host
service: port
protocol: s];
}
else
{
host = [request objectForKey: GSHTTPPropertyProxyHostKey];
port = [request objectForKey: GSHTTPPropertyProxyPortKey];
sock = [NSFileHandle
fileHandleAsClientInBackgroundAtAddress: host
service: port
protocol: s];
}
}
if (sock == nil)
{
extern int errno;
/*
* Tell superclass that the load failed - let it do housekeeping.
*/
[self backgroundLoadDidFailWithReason: [NSString stringWithFormat:
@"Unable to connect to %@:%@ ... %s",
host, port, GSLastErrorStr(errno)]];
return;
}
RETAIN(sock);
nc = [NSNotificationCenter defaultCenter];
[nc addObserver: self
selector: @selector(bgdConnect:)
name: GSFileHandleConnectCompletionNotification
object: sock];
connectionState = connecting;
}
else
{
NSString *method;
NSString *path;
NSString *basic;
keepalive = YES; // Reusing a connection.
method = [request objectForKey: GSHTTPPropertyMethodKey];
if (method == nil)
{
if ([wData length] > 0)
{
method = @"POST";
}
else
{
method = @"GET";
}
}
path = [[u path] stringByTrimmingSpaces];
if ([path length] == 0)
{
path = @"/";
}
basic = [NSString stringWithFormat: @"%@ %@", method, path];
[self bgdApply: basic];
}
}
/**
* Writes the specified data as the body of an <code>http</code>
* or <code>https</code> request to the web server.