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/GSArray.m: Make perform methods enumerate forwards.
* Source/Additions/GSXML.m: ([nodeAtIndex:]) fix memory leak. * 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> 2004-09-22 Richard Frith-Macdonald <rfm@gnu.org>

View file

@ -57,8 +57,10 @@ static NSString *httpVersion = @"1.1";
{ {
BOOL tunnel; BOOL tunnel;
BOOL debug; BOOL debug;
BOOL keepalive;
NSFileHandle *sock; NSFileHandle *sock;
NSURL *url; NSURL *url;
NSURL *u;
NSMutableData *dat; NSMutableData *dat;
GSMimeParser *parser; GSMimeParser *parser;
GSMimeDocument *document; GSMimeDocument *document;
@ -67,6 +69,7 @@ static NSString *httpVersion = @"1.1";
NSData *wData; NSData *wData;
NSMutableDictionary *request; NSMutableDictionary *request;
unsigned int bodyPos; unsigned int bodyPos;
unsigned int redirects;
enum { enum {
idle, idle,
connecting, connecting,
@ -75,6 +78,7 @@ static NSString *httpVersion = @"1.1";
} connectionState; } connectionState;
} }
- (void) setDebug: (BOOL)flag; - (void) setDebug: (BOOL)flag;
- (void) _tryLoadInBackground: (NSURL*)fromURL;
@end @end
/** /**
@ -251,6 +255,7 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
- (void) dealloc - (void) dealloc
{ {
RELEASE(sock); RELEASE(sock);
RELEASE(u);
RELEASE(url); RELEASE(url);
RELEASE(dat); RELEASE(dat);
RELEASE(parser); RELEASE(parser);
@ -306,10 +311,12 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
NSMutableData *buf; NSMutableData *buf;
NSString *version; NSString *version;
if (debug == YES) NSLog(@"%@", NSStringFromSelector(_cmd));
s = [basic mutableCopy]; 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]; version = [request objectForKey: NSHTTPPropertyServerHTTPVersionKey];
@ -321,7 +328,7 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
if ([wProperties objectForKey: @"host"] == nil) if ([wProperties objectForKey: @"host"] == nil)
{ {
[wProperties setObject: [url host] forKey: @"host"]; [wProperties setObject: [u host] forKey: @"host"];
} }
if ([wData length] > 0) if ([wData length] > 0)
@ -339,18 +346,18 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
} }
if ([wProperties objectForKey: @"authorisation"] == nil) if ([wProperties objectForKey: @"authorisation"] == nil)
{ {
if ([url user] != nil) if ([u user] != nil)
{ {
NSString *auth; NSString *auth;
if ([[url password] length] > 0) if ([[u password] length] > 0)
{ {
auth = [NSString stringWithFormat: @"%@:%@", auth = [NSString stringWithFormat: @"%@:%@",
[url user], [url password]]; [u user], [u password]];
} }
else else
{ {
auth = [NSString stringWithFormat: @"%@", [url user]]; auth = [NSString stringWithFormat: @"%@", [u user]];
} }
auth = [NSString stringWithFormat: @"Basic %@", auth = [NSString stringWithFormat: @"Basic %@",
[GSMimeDocument encodeBase64String: auth]]; [GSMimeDocument encodeBase64String: auth]];
@ -374,17 +381,8 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
if (wData != nil) if (wData != nil)
{ {
[buf appendData: wData]; [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. * Watch for write completion.
*/ */
@ -393,6 +391,14 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
name: GSFileHandleWriteCompletionNotification name: GSFileHandleWriteCompletionNotification
object: sock]; object: sock];
connectionState = writing; connectionState = writing;
/*
* Send request to server.
*/
if (debug == YES) debugWrite(self, buf);
[sock writeInBackgroundAndNotify: buf];
RELEASE(buf);
RELEASE(s);
} }
- (void) bgdRead: (NSNotification*) not - (void) bgdRead: (NSNotification*) not
@ -490,6 +496,7 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
d = [parser data]; d = [parser data];
r = NSMakeRange(bodyPos, [d length] - bodyPos); r = NSMakeRange(bodyPos, [d length] - bodyPos);
bodyPos = 0; bodyPos = 0;
DESTROY(wData);
[self didLoadBytes: [d subdataWithRange: r] [self didLoadBytes: [d subdataWithRange: r]
loadComplete: YES]; loadComplete: YES];
} }
@ -557,172 +564,12 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
- (void) loadInBackground - (void) loadInBackground
{ {
NSNotificationCenter *nc; [self _tryLoadInBackground: nil];
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;
} }
- (void) endLoadInBackground - (void) endLoadInBackground
{ {
DESTROY(wData);
if (connectionState != idle) if (connectionState != idle)
{ {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
@ -753,7 +600,9 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
NSString *method; NSString *method;
NSString *path; NSString *path;
path = [[url path] stringByTrimmingSpaces]; if (debug == YES) NSLog(@"%@", NSStringFromSelector(_cmd));
path = [[u path] stringByTrimmingSpaces];
if ([path length] == 0) if ([path length] == 0)
{ {
path = @"/"; path = @"/";
@ -786,7 +635,7 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
/* /*
* If SSL via proxy, set up tunnel first * If SSL via proxy, set up tunnel first
*/ */
if ([[url scheme] isEqualToString: @"https"] if ([[u scheme] isEqualToString: @"https"]
&& [[request objectForKey: GSHTTPPropertyProxyHostKey] length] > 0) && [[request objectForKey: GSHTTPPropertyProxyHostKey] length] > 0)
{ {
NSRunLoop *loop = [NSRunLoop currentRunLoop]; NSRunLoop *loop = [NSRunLoop currentRunLoop];
@ -803,15 +652,15 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
{ {
version = httpVersion; version = httpVersion;
} }
if ([url port] == nil) if ([u port] == nil)
{ {
cmd = [NSString stringWithFormat: @"CONNECT %@:443 HTTP/%@\r\n\r\n", cmd = [NSString stringWithFormat: @"CONNECT %@:443 HTTP/%@\r\n\r\n",
[url host], version]; [u host], version];
} }
else else
{ {
cmd = [NSString stringWithFormat: @"CONNECT %@:%@ HTTP/%@\r\n\r\n", 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]; object: sock];
buf = [cmd dataUsingEncoding: NSASCIIStringEncoding]; buf = [cmd dataUsingEncoding: NSASCIIStringEncoding];
[sock writeInBackgroundAndNotify: buf];
if (debug == YES) debugWrite(self, buf); if (debug == YES) debugWrite(self, buf);
[sock writeInBackgroundAndNotify: buf];
when = [NSDate alloc]; when = [NSDate alloc];
while (tunnel == YES) while (tunnel == YES)
@ -855,7 +704,7 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
return; return;
} }
} }
if ([[url scheme] isEqualToString: @"https"]) if ([[u scheme] isEqualToString: @"https"])
{ {
/* /*
* If we are an https connection, negotiate secure connection * 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 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://%@%@", s = [[NSMutableString alloc] initWithFormat: @"%@ http://%@%@",
method, [url host], path]; method, [u host], path];
} }
else else
{ {
s = [[NSMutableString alloc] initWithFormat: @"%@ http://%@:%@%@", s = [[NSMutableString alloc] initWithFormat: @"%@ http://%@:%@%@",
method, [url host], [url port], path]; method, [u host], [u port], path];
} }
} }
else // no proxy else // no proxy
@ -910,13 +759,32 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
- (void) bgdWrite: (NSNotification*)notification - (void) bgdWrite: (NSNotification*)notification
{ {
NSNotificationCenter *nc;
NSDictionary *userInfo = [notification userInfo]; NSDictionary *userInfo = [notification userInfo];
NSString *e; NSString *e;
if (debug == YES) NSLog(@"%@", NSStringFromSelector(_cmd));
e = [userInfo objectForKey: GSFileHandleNotificationError]; e = [userInfo objectForKey: GSFileHandleNotificationError];
if (e != nil) if (e != nil)
{ {
tunnel = NO; 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); NSLog(@"Failed to write command to socket - %@", e);
/* /*
* Tell superclass that the load failed - let it do housekeeping. * Tell superclass that the load failed - let it do housekeeping.
@ -927,8 +795,6 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
} }
else else
{ {
NSNotificationCenter *nc;
/* /*
* Don't watch for write completions any more. * Don't watch for write completions any more.
*/ */
@ -1030,6 +896,192 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
debug = flag; 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> * Writes the specified data as the body of an <code>http</code>
* or <code>https</code> request to the web server. * or <code>https</code> request to the web server.