tweaks for url loading

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@25142 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Richard Frith-MacDonald 2007-05-11 15:47:06 +00:00
parent 7c633ab823
commit 1d562005d1
9 changed files with 259 additions and 240 deletions

View file

@ -5,9 +5,11 @@
* Source/win32/NSStreamWin32.m: * Source/win32/NSStreamWin32.m:
* Headers/Foundation/NSStream.h: * Headers/Foundation/NSStream.h:
First hack at extensions to get address and port properties for First hack at extensions to get address and port properties for
network streams. network streams. Use localhost if no host is given for connect.
* Source/GSFFIInvocation.m: * Source/GSFFIInvocation.m:
Attempt to use forward2 if available. Attempt to use forward2 if available.
* Source/NSURLProtocol.m:
Fixup some problems with incorporation of code from mySTEP.
2007-05-11 Richard Frith-Macdonald <rfm@gnu.org> 2007-05-11 Richard Frith-Macdonald <rfm@gnu.org>

View file

@ -442,6 +442,7 @@ extern "C" {
#include <Foundation/NSURLHandle.h> #include <Foundation/NSURLHandle.h>
#include <Foundation/NSURLConnection.h>
@class NSArray; @class NSArray;
@class NSDictionary; @class NSDictionary;
@ -504,13 +505,10 @@ extern "C" {
@interface GSXMLRPC : NSObject <NSURLHandleClient> @interface GSXMLRPC : NSObject <NSURLHandleClient>
{ {
@private @private
#ifdef GNUSTEP
NSURLHandle *handle; NSURLHandle *handle;
#else
NSString *connectionURL; NSString *connectionURL;
NSURLConnection *connection; NSURLConnection *connection;
NSMutableData *response; NSMutableData *response;
#endif
NSTimer *timer; NSTimer *timer;
id result; id result;
id delegate; // Not retained. id delegate; // Not retained.

View file

@ -72,7 +72,7 @@ extern "C" {
/** <init /> /** <init />
* Initialises the receiver with the specified request (performing * Initialises the receiver with the specified request (performing
* a deep copy so that ithe request does not change during loading) * a deep copy so that the request does not change during loading)
* and delegate.<br /> * and delegate.<br />
* This automatically initiates an asynchronous load for the request.<br /> * This automatically initiates an asynchronous load for the request.<br />
* Processing of the request is done in the thread which calls this * Processing of the request is done in the thread which calls this

View file

@ -52,29 +52,7 @@
#include "GNUstepBase/GSMime.h" #include "GNUstepBase/GSMime.h"
#include "GNUstepBase/GSXML.h" #include "GNUstepBase/GSXML.h"
#ifndef NeXT_Foundation_LIBRARY
#include <Foundation/NSArray.h>
#include <Foundation/NSBundle.h>
#include <Foundation/NSCalendarDate.h>
#include <Foundation/NSCharacterSet.h>
#include <Foundation/NSData.h>
#include <Foundation/NSDate.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSException.h>
#include <Foundation/NSFileManager.h>
#include <Foundation/NSHashTable.h>
#include <Foundation/NSInvocation.h>
#include <Foundation/NSMapTable.h>
#include <Foundation/NSRunLoop.h>
#include <Foundation/NSString.h>
#include <Foundation/NSTimeZone.h>
#include <Foundation/NSTimer.h>
#include <Foundation/NSURL.h>
#include <Foundation/NSURLHandle.h>
#include <Foundation/NSValue.h>
#else
#include <Foundation/Foundation.h> #include <Foundation/Foundation.h>
#endif
/* libxml headers */ /* libxml headers */
#include <libxml/tree.h> #include <libxml/tree.h>

View file

@ -66,7 +66,7 @@
- (void) _setHeaders: (id)headers; - (void) _setHeaders: (id)headers;
- (void) _setStatusCode: (int)code text: (NSString*)text; - (void) _setStatusCode: (int)code text: (NSString*)text;
- (void) _setValue: (NSString *)value forHTTPHeaderField: (NSString *)field; - (void) _setValue: (NSString *)value forHTTPHeaderField: (NSString *)field;
- (NSString *) _valueForHTTPHeaderField: (NSString *)field; - (NSString*) _valueForHTTPHeaderField: (NSString*)field;
@end @end

View file

@ -74,16 +74,27 @@ typedef struct {
static NSMutableArray *registered = nil; static NSMutableArray *registered = nil;
static NSLock *regLock = nil; static NSLock *regLock = nil;
static Class abstractClass = nil;
static NSURLProtocol *placeholder = nil;
@implementation NSURLProtocol @implementation NSURLProtocol
+ (id) allocWithZone: (NSZone*)z + (id) allocWithZone: (NSZone*)z
{ {
NSURLProtocol *o = [super allocWithZone: z]; NSURLProtocol *o;
if (o != nil) if ((self == abstractClass) && (z == 0 || z == NSDefaultMallocZone()))
{ {
o->_NSURLProtocolInternal = NSZoneCalloc(z, 1, sizeof(Internal)); /* Return a default placeholder instance to avoid the overhead of
* creating and destroying instances of the abstract class.
*/
o = placeholder;
}
else
{
/* Create and return an instance of the concrete subclass.
*/
o = (NSURLProtocol*)NSAllocateObject(self, 0, z);
} }
return o; return o;
} }
@ -92,6 +103,9 @@ static NSLock *regLock = nil;
{ {
if (registered == nil) if (registered == nil)
{ {
abstractClass = [NSURLProtocol class];
placeholder = (NSURLProtocol*)NSAllocateObject(abstractClass, 0,
NSDefaultMallocZone());
registered = [NSMutableArray new]; registered = [NSMutableArray new];
regLock = [NSLock new]; regLock = [NSLock new];
[self registerClass: [_NSHTTPURLProtocol class]]; [self registerClass: [_NSHTTPURLProtocol class]];
@ -145,6 +159,11 @@ static NSLock *regLock = nil;
- (void) dealloc - (void) dealloc
{ {
if (self == placeholder)
{
[self retain];
return;
}
if (this != 0) if (this != 0)
{ {
[self stopLoading]; [self stopLoading];
@ -153,6 +172,7 @@ static NSLock *regLock = nil;
RELEASE(this->cachedResponse); RELEASE(this->cachedResponse);
RELEASE(this->request); RELEASE(this->request);
NSZoneFree([self zone], this); NSZoneFree([self zone], this);
_NSURLProtocolInternal = 0;
} }
[super dealloc]; [super dealloc];
} }
@ -160,14 +180,27 @@ static NSLock *regLock = nil;
- (NSString*) description - (NSString*) description
{ {
return [NSString stringWithFormat:@"%@ %@", return [NSString stringWithFormat:@"%@ %@",
[super description], this->request]; [super description], this ? (id)this->request : nil];
}
- (id) init
{
if ((self = [super init]) != nil)
{
if (isa != abstractClass)
{
_NSURLProtocolInternal = NSZoneCalloc(GSObjCZone(self),
1, sizeof(Internal));
}
}
return self;
} }
- (id) initWithRequest: (NSURLRequest *)request - (id) initWithRequest: (NSURLRequest *)request
cachedResponse: (NSCachedURLResponse *)cachedResponse cachedResponse: (NSCachedURLResponse *)cachedResponse
client: (id <NSURLProtocolClient>)client client: (id <NSURLProtocolClient>)client
{ {
if (isa == [NSURLProtocol class]) if (isa == abstractClass)
{ {
unsigned count; unsigned count;
@ -189,7 +222,7 @@ static NSLock *regLock = nil;
cachedResponse: cachedResponse cachedResponse: cachedResponse
client: client]; client: client];
} }
if ((self = [super init]) != nil) if ((self = [self init]) != nil)
{ {
this->request = [request copy]; this->request = [request copy];
this->cachedResponse = RETAIN(cachedResponse); this->cachedResponse = RETAIN(cachedResponse);
@ -390,82 +423,83 @@ static NSLock *regLock = nil;
{ // process header line { // process header line
unsigned char *c, *end; unsigned char *c, *end;
NSString *key, *val; NSString *key, *val;
#if 0 #if 1
NSLog(@"process header line len=%d", len); NSLog(@"process header line len=%d", len);
#endif #endif
// if it begins with ' ' or '\t' it is a continuation line to the previous header field // if it begins with ' ' or '\t' it is a continuation line to the previous header field
if (!_headers) if (!_headers)
{ // should be/must be the header line { // should be/must be the header line
unsigned major, minor; unsigned major, minor;
if (sscanf((char *) buffer, "HTTP/%u.%u %u", &major, &minor, &_statusCode) == 3) if (sscanf((char *) buffer, "HTTP/%u.%u %u", &major, &minor, &_statusCode) == 3)
{ // response header line { // response header line
if (major != 1 || minor > 1) if (major != 1 || minor > 1)
[this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"Bad HTTP version" code: 0 userInfo: nil]]; [this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"Bad HTTP version" code: 0 userInfo: nil]];
// must be first - but must also be present and valid before we go to receive the body! // must be first - but must also be present and valid before we go to receive the body!
_headers=[NSMutableDictionary dictionaryWithCapacity: 10]; // start collecting headers _headers=[NSMutableDictionary dictionaryWithCapacity: 10]; // start collecting headers
// if (_statusCode >= 400 && _statusCode <= 499) // if (_statusCode >= 400 && _statusCode <= 499)
NSLog(@"Client header: %.*s", len, buffer); NSLog(@"Client header: %.*s", len, buffer);
return NO; // process next line return NO; // process next line
} }
else else
; // invalid header ; // invalid header
return NO; // process next line return NO; // process next line
} }
if (len == 0) if (len == 0)
{ // empty line, i.e. end of header { // empty line, i.e. end of header
NSString *loc; NSString *loc;
NSHTTPURLResponse *response; NSHTTPURLResponse *response;
response = [[NSHTTPURLResponse alloc] initWithURL: [this->request URL] response = [[NSHTTPURLResponse alloc] initWithURL: [this->request URL]
MIMEType: nil MIMEType: nil
expectedContentLength: -1 expectedContentLength: -1
textEncodingName: nil]; textEncodingName: nil];
[response _setHeaders: _headers]; [response _setHeaders: _headers];
DESTROY(_headers); DESTROY(_headers);
[response _setStatusCode: _statusCode text: @""]; [response _setStatusCode: _statusCode text: @""];
loc = [response _valueForHTTPHeaderField: @"location"]; loc = [response _valueForHTTPHeaderField: @"location"];
if ([loc length]) if ([loc length])
{ // Location: entry exists { // Location: entry exists
NSURLRequest *request=[NSURLRequest requestWithURL: [NSURL URLWithString: loc]]; NSURLRequest *request=[NSURLRequest requestWithURL: [NSURL URLWithString: loc]];
if (!request) if (!request)
[this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"Invalid redirect request" code: 0 userInfo: nil]]; // error [this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"Invalid redirect request" code: 0 userInfo: nil]]; // error
[this->client URLProtocol: self wasRedirectedToRequest: request redirectResponse: response]; [this->client URLProtocol: self wasRedirectedToRequest: request redirectResponse: response];
} }
else else
{ {
NSURLCacheStoragePolicy policy=NSURLCacheStorageAllowed; // default NSURLCacheStoragePolicy policy=NSURLCacheStorageAllowed; // default
// read from [this->request cachePolicy]; // read from [this->request cachePolicy];
/* /*
NSURLCacheStorageAllowed, NSURLCacheStorageAllowed,
NSURLCacheStorageAllowedInMemoryOnly NSURLCacheStorageAllowedInMemoryOnly
NSURLCacheStorageNotAllowed NSURLCacheStorageNotAllowed
*/ */
if ([self isKindOfClass: [_NSHTTPSURLProtocol class]]) if ([self isKindOfClass: [_NSHTTPSURLProtocol class]])
policy=NSURLCacheStorageNotAllowed; // never policy=NSURLCacheStorageNotAllowed; // never
[this->client URLProtocol: self didReceiveResponse: response cacheStoragePolicy: policy]; NSLog(@"Received");
} [this->client URLProtocol: self didReceiveResponse: response cacheStoragePolicy: policy];
return YES; }
} return YES;
}
for (c=buffer, end=c+len; *c != ':'; c++) for (c=buffer, end=c+len; *c != ':'; c++)
{ {
if (c == end) if (c == end)
{ // no colon found! { // no colon found!
// raise bad header error or simply ignore? // raise bad header error or simply ignore?
return NO; // keep processing header lines return NO; // keep processing header lines
} }
} }
key=[[NSString stringWithCString: (char *) buffer length: c-buffer] capitalizedString]; key=[[NSString stringWithCString: (char *) buffer length: c-buffer] capitalizedString];
while(++c < end && (*c == ' ' || *c == '\t')) while(++c < end && (*c == ' ' || *c == '\t'))
; // skip spaces ; // skip spaces
val=[NSString stringWithCString: (char *) c length: end-c]; val=[NSString stringWithCString: (char *) c length: end-c];
[_headers setObject: val forKey: [key lowercaseString]]; [_headers setObject: val forKey: [key lowercaseString]];
return NO; // not yet done return NO; // not yet done
} }
- (void) _processHeader: (unsigned char *) buffer length: (int) len - (void) _processHeader: (unsigned char *) buffer length: (int) len
{ // next header fragment received { // next header fragment received
unsigned char *ptr, *end; unsigned char *ptr, *end;
#if 0 #if 1
NSLog(@"received %d bytes", len); NSLog(@"received %d bytes", len);
#endif #endif
if (len <= 0) if (len <= 0)
@ -534,147 +568,156 @@ static NSLock *regLock = nil;
#if 0 #if 0
NSLog(@"stream: %@ handleEvent: %x for: %@", stream, event, self); NSLog(@"stream: %@ handleEvent: %x for: %@", stream, event, self);
#endif #endif
if (stream == this->input) if (stream == this->input)
{ {
switch(event)
{
case NSStreamEventHasBytesAvailable:
{
unsigned char buffer[512];
int len=[(NSInputStream *) stream read: buffer maxLength: sizeof(buffer)];
if (len < 0)
{
#if 1 #if 1
NSLog(@"receive error %@", [NSError _last]); NSLog(@"input stream handleEvent: %x for: %@", event, self);
#endif #endif
[this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"receive error" code: 0 userInfo: nil]]; switch(event)
[self _unschedule]; {
return; case NSStreamEventHasBytesAvailable:
} {
if (_readingBody) unsigned char buffer[512];
[this->client URLProtocol: self didLoadData: [NSData dataWithBytes: buffer length: len]]; // notify int len;
else
[self _processHeader: buffer length: len];
return;
}
case NSStreamEventEndEncountered: // can this occur in parallel to NSStreamEventHasBytesAvailable???
{
#if 0
NSLog(@"end of response");
#endif
if (!_readingBody)
[this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"incomplete header" code: 0 userInfo: nil]];
[this->client URLProtocolDidFinishLoading: self];
_readingBody=NO;
[self _unschedule];
return;
}
case NSStreamEventOpenCompleted:
{ // prepare to receive header
#if 0
NSLog(@"HTTP input stream opened");
#endif
return;
}
default:
break;
}
}
else if (stream == this->output)
{
unsigned char *msg;
#if 0
NSLog(@"An event occurred on the output stream.");
#endif
/* e.g.
POST /wiki/Spezial: Search HTTP/1.1
Host: de.wikipedia.org
Content-Type: application/x-www-form-urlencoded
Content-Length: 24
search=Katzen&go=Artikel <- body len = [(NSInputStream *)stream read: buffer
*/ maxLength: sizeof(buffer)];
switch(event) if (len < 0)
{ {
case NSStreamEventOpenCompleted: #if 1
{ NSLog(@"receive error %@", [NSError _last]);
#if 0 #endif
NSLog(@"HTTP output stream opened"); [this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"receive error" code: 0 userInfo: nil]];
#endif [self _unschedule];
msg=(unsigned char *) [[NSString stringWithFormat: @"%@ %@ HTTP/1.1\r\n", return;
[this->request HTTPMethod], }
[[this->request URL] absoluteString] if (_readingBody)
] cString]; // FIXME: UTF8??? [this->client URLProtocol: self didLoadData: [NSData dataWithBytes: buffer length: len]]; // notify
[(NSOutputStream *) stream write: msg maxLength: strlen((char *) msg)]; else
#if 1 [self _processHeader: buffer length: len];
NSLog(@"sent %s", msg); return;
#endif }
_headerEnumerator=[[[this->request allHTTPHeaderFields] objectEnumerator] retain]; case NSStreamEventEndEncountered: // can this occur in parallel to NSStreamEventHasBytesAvailable???
return; {
} #if 1
case NSStreamEventHasSpaceAvailable: NSLog(@"end of response");
{ #endif
// FIXME: should also send out relevant Cookies if (!_readingBody)
if (_headerEnumerator) [this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"incomplete header" code: 0 userInfo: nil]];
{ // send next header [this->client URLProtocolDidFinishLoading: self];
NSString *key; _readingBody=NO;
key=[_headerEnumerator nextObject]; [self _unschedule];
if (key) return;
{ }
#if 1 case NSStreamEventOpenCompleted:
NSLog(@"sending %@: %@", key, [this->request valueForHTTPHeaderField: key]); { // prepare to receive header
#endif #if 1
msg=(unsigned char *)[[NSString stringWithFormat: @"%@: %@\r\n", key, [this->request valueForHTTPHeaderField: key]] UTF8String]; NSLog(@"HTTP input stream opened");
} #endif
else return;
{ // was last header entry }
[_headerEnumerator release]; default:
_headerEnumerator=nil; break;
msg=(unsigned char *) "\r\n"; // send empty line }
_body=[[this->request HTTPBodyStream] retain]; // if present }
if (!_body && [this->request HTTPBody]) else if (stream == this->output)
_body=[[NSInputStream alloc] initWithData: [this->request HTTPBody]]; // prepare to send request body {
[_body open]; unsigned char *msg;
}
[(NSOutputStream *) stream write: msg maxLength: strlen((char *) msg)]; // NOTE: we might block here if header value is too long #if 0
#if 1 NSLog(@"An event occurred on the output stream.");
NSLog(@"sent %s", msg); #endif
#endif /* e.g.
return; POST /wiki/Spezial: Search HTTP/1.1
} Host: de.wikipedia.org
else if (_body) Content-Type: application/x-www-form-urlencoded
{ // send (next part of) body until done Content-Length: 24
if ([_body hasBytesAvailable])
{ search=Katzen&go=Artikel <- body
unsigned char buffer[512]; */
int len=[_body read: buffer maxLength: sizeof(buffer)]; // read next block from stream
if (len < 0) switch(event)
{ {
#if 1 case NSStreamEventOpenCompleted:
NSLog(@"error reading from HTTPBody stream %@", [NSError _last]); {
#endif #if 0
[self _unschedule]; NSLog(@"HTTP output stream opened");
return; #endif
} msg = (unsigned char *)[[NSString stringWithFormat:
[(NSOutputStream *) stream write: buffer maxLength: len]; // send @"%@ %@ HTTP/1.1\r\n",
} [this->request HTTPMethod],
else [[this->request URL] absoluteString]] UTF8String];
{ // done [(NSOutputStream *) stream write: msg
#if 0 maxLength: strlen((char *) msg)];
NSLog(@"request sent"); #if 0
#endif NSLog(@"sent %s", msg);
[self _unschedule]; // well, we should just unschedule the send stream #endif
[_body close]; _headerEnumerator = [[[this->request allHTTPHeaderFields] keyEnumerator] retain];
[_body release]; return;
_body=nil; }
} case NSStreamEventHasSpaceAvailable:
} {
return; // done // FIXME: should also send out relevant Cookies
} if (_headerEnumerator)
default: { // send next header
break; NSString *key;
} key = [_headerEnumerator nextObject];
} if (key)
{
#if 0
NSLog(@"sending %@: %@", key, [this->request valueForHTTPHeaderField: key]);
#endif
msg=(unsigned char *)[[NSString stringWithFormat: @"%@: %@\r\n", key, [this->request valueForHTTPHeaderField: key]] UTF8String];
}
else
{ // was last header entry
[_headerEnumerator release];
_headerEnumerator=nil;
msg=(unsigned char *) "\r\n"; // send empty line
_body=[[this->request HTTPBodyStream] retain]; // if present
if (!_body && [this->request HTTPBody])
_body=[[NSInputStream alloc] initWithData: [this->request HTTPBody]]; // prepare to send request body
[_body open];
}
[(NSOutputStream *) stream write: msg maxLength: strlen((char *) msg)]; // NOTE: we might block here if header value is too long
#if 0
NSLog(@"sent %s", msg);
#endif
return;
}
else if (_body)
{ // send (next part of) body until done
if ([_body hasBytesAvailable])
{
unsigned char buffer[512];
int len=[_body read: buffer maxLength: sizeof(buffer)]; // read next block from stream
if (len < 0)
{
#if 1
NSLog(@"error reading from HTTPBody stream %@", [NSError _last]);
#endif
[self _unschedule];
return;
}
[(NSOutputStream *) stream write: buffer maxLength: len]; // send
}
else
{ // done
#if 0
NSLog(@"request sent");
#endif
[self _unschedule]; // well, we should just unschedule the send stream
[_body close];
[_body release];
_body=nil;
}
}
return; // done
}
default:
break;
}
}
NSLog(@"An error %@ occurred on the event %08x of stream %@ of %@", [stream streamError], event, stream, self); NSLog(@"An error %@ occurred on the event %08x of stream %@ of %@", [stream streamError], event, stream, self);
[this->client URLProtocol: self didFailWithError: [stream streamError]]; [this->client URLProtocol: self didFailWithError: [stream streamError]];
} }

View file

@ -399,6 +399,5 @@ static const NSMapTableKeyCallBacks headerKeyCallBacks =
{ {
return this->statusCode; return this->statusCode;
} }
@end @end

View file

@ -1268,7 +1268,7 @@ static void setNonblocking(int fd)
inputStream: (NSInputStream **)inputStream inputStream: (NSInputStream **)inputStream
outputStream: (NSOutputStream **)outputStream outputStream: (NSOutputStream **)outputStream
{ {
NSString *address = [host address]; NSString *address = host ? (id)[host address] : (id)@"127.0.0.1";
GSSocketInputStream *ins = nil; GSSocketInputStream *ins = nil;
GSSocketOutputStream *outs = nil; GSSocketOutputStream *outs = nil;
int sock; int sock;
@ -1281,10 +1281,10 @@ static void setNonblocking(int fd)
if (!ins) if (!ins)
{ {
#if defined(PF_INET6) #if defined(PF_INET6)
ins = [[GSInet6InputStream alloc] initToAddr: address ins = AUTORELEASE([[GSInet6InputStream alloc]
port: port]; initToAddr: address port: port]);
outs = [[GSInet6OutputStream alloc] initToAddr: address outs = AUTORELEASE([[GSInet6OutputStream alloc]
port: port]; initToAddr: address port: port]);
sock = socket(PF_INET6, SOCK_STREAM, 0); sock = socket(PF_INET6, SOCK_STREAM, 0);
#else #else
sock = -1; sock = -1;
@ -1308,7 +1308,6 @@ static void setNonblocking(int fd)
[outs setSibling: ins]; [outs setSibling: ins];
*outputStream = outs; *outputStream = outs;
} }
return;
} }
+ (void) getLocalStreamsToPath: (NSString *)path + (void) getLocalStreamsToPath: (NSString *)path

View file

@ -1926,7 +1926,7 @@ static id propertyForInet6Stream(int descriptor, NSString *key)
inputStream: (NSInputStream **)inputStream inputStream: (NSInputStream **)inputStream
outputStream: (NSOutputStream **)outputStream outputStream: (NSOutputStream **)outputStream
{ {
NSString *address = [host address]; NSString *address = host ? (id)[host address] : (id)@"127.0.0.1";
GSSocketInputStream *ins = nil; GSSocketInputStream *ins = nil;
GSSocketOutputStream *outs = nil; GSSocketOutputStream *outs = nil;
int sock; int sock;