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:
* Headers/Foundation/NSStream.h:
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:
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>

View file

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

View file

@ -72,7 +72,7 @@ extern "C" {
/** <init />
* 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 />
* This automatically initiates an asynchronous load for the request.<br />
* Processing of the request is done in the thread which calls this

View file

@ -52,29 +52,7 @@
#include "GNUstepBase/GSMime.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>
#endif
/* libxml headers */
#include <libxml/tree.h>

View file

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

View file

@ -74,16 +74,27 @@ typedef struct {
static NSMutableArray *registered = nil;
static NSLock *regLock = nil;
static Class abstractClass = nil;
static NSURLProtocol *placeholder = nil;
@implementation NSURLProtocol
+ (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;
}
@ -92,6 +103,9 @@ static NSLock *regLock = nil;
{
if (registered == nil)
{
abstractClass = [NSURLProtocol class];
placeholder = (NSURLProtocol*)NSAllocateObject(abstractClass, 0,
NSDefaultMallocZone());
registered = [NSMutableArray new];
regLock = [NSLock new];
[self registerClass: [_NSHTTPURLProtocol class]];
@ -145,6 +159,11 @@ static NSLock *regLock = nil;
- (void) dealloc
{
if (self == placeholder)
{
[self retain];
return;
}
if (this != 0)
{
[self stopLoading];
@ -153,6 +172,7 @@ static NSLock *regLock = nil;
RELEASE(this->cachedResponse);
RELEASE(this->request);
NSZoneFree([self zone], this);
_NSURLProtocolInternal = 0;
}
[super dealloc];
}
@ -160,14 +180,27 @@ static NSLock *regLock = nil;
- (NSString*) description
{
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
cachedResponse: (NSCachedURLResponse *)cachedResponse
client: (id <NSURLProtocolClient>)client
{
if (isa == [NSURLProtocol class])
if (isa == abstractClass)
{
unsigned count;
@ -189,7 +222,7 @@ static NSLock *regLock = nil;
cachedResponse: cachedResponse
client: client];
}
if ((self = [super init]) != nil)
if ((self = [self init]) != nil)
{
this->request = [request copy];
this->cachedResponse = RETAIN(cachedResponse);
@ -390,82 +423,83 @@ static NSLock *regLock = nil;
{ // process header line
unsigned char *c, *end;
NSString *key, *val;
#if 0
#if 1
NSLog(@"process header line len=%d", len);
#endif
// if it begins with ' ' or '\t' it is a continuation line to the previous header field
if (!_headers)
{ // should be/must be the header line
unsigned major, minor;
if (sscanf((char *) buffer, "HTTP/%u.%u %u", &major, &minor, &_statusCode) == 3)
{ // response header line
if (major != 1 || minor > 1)
[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!
_headers=[NSMutableDictionary dictionaryWithCapacity: 10]; // start collecting headers
// if (_statusCode >= 400 && _statusCode <= 499)
NSLog(@"Client header: %.*s", len, buffer);
return NO; // process next line
}
else
; // invalid header
return NO; // process next line
}
{ // should be/must be the header line
unsigned major, minor;
if (sscanf((char *) buffer, "HTTP/%u.%u %u", &major, &minor, &_statusCode) == 3)
{ // response header line
if (major != 1 || minor > 1)
[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!
_headers=[NSMutableDictionary dictionaryWithCapacity: 10]; // start collecting headers
// if (_statusCode >= 400 && _statusCode <= 499)
NSLog(@"Client header: %.*s", len, buffer);
return NO; // process next line
}
else
; // invalid header
return NO; // process next line
}
if (len == 0)
{ // empty line, i.e. end of header
NSString *loc;
NSHTTPURLResponse *response;
{ // empty line, i.e. end of header
NSString *loc;
NSHTTPURLResponse *response;
response = [[NSHTTPURLResponse alloc] initWithURL: [this->request URL]
MIMEType: nil
expectedContentLength: -1
textEncodingName: nil];
[response _setHeaders: _headers];
DESTROY(_headers);
[response _setStatusCode: _statusCode text: @""];
loc = [response _valueForHTTPHeaderField: @"location"];
if ([loc length])
{ // Location: entry exists
NSURLRequest *request=[NSURLRequest requestWithURL: [NSURL URLWithString: loc]];
if (!request)
[this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"Invalid redirect request" code: 0 userInfo: nil]]; // error
[this->client URLProtocol: self wasRedirectedToRequest: request redirectResponse: response];
}
else
{
NSURLCacheStoragePolicy policy=NSURLCacheStorageAllowed; // default
// read from [this->request cachePolicy];
/*
NSURLCacheStorageAllowed,
NSURLCacheStorageAllowedInMemoryOnly
NSURLCacheStorageNotAllowed
*/
if ([self isKindOfClass: [_NSHTTPSURLProtocol class]])
policy=NSURLCacheStorageNotAllowed; // never
[this->client URLProtocol: self didReceiveResponse: response cacheStoragePolicy: policy];
}
return YES;
}
response = [[NSHTTPURLResponse alloc] initWithURL: [this->request URL]
MIMEType: nil
expectedContentLength: -1
textEncodingName: nil];
[response _setHeaders: _headers];
DESTROY(_headers);
[response _setStatusCode: _statusCode text: @""];
loc = [response _valueForHTTPHeaderField: @"location"];
if ([loc length])
{ // Location: entry exists
NSURLRequest *request=[NSURLRequest requestWithURL: [NSURL URLWithString: loc]];
if (!request)
[this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"Invalid redirect request" code: 0 userInfo: nil]]; // error
[this->client URLProtocol: self wasRedirectedToRequest: request redirectResponse: response];
}
else
{
NSURLCacheStoragePolicy policy=NSURLCacheStorageAllowed; // default
// read from [this->request cachePolicy];
/*
NSURLCacheStorageAllowed,
NSURLCacheStorageAllowedInMemoryOnly
NSURLCacheStorageNotAllowed
*/
if ([self isKindOfClass: [_NSHTTPSURLProtocol class]])
policy=NSURLCacheStorageNotAllowed; // never
NSLog(@"Received");
[this->client URLProtocol: self didReceiveResponse: response cacheStoragePolicy: policy];
}
return YES;
}
for (c=buffer, end=c+len; *c != ':'; c++)
{
if (c == end)
{ // no colon found!
// raise bad header error or simply ignore?
return NO; // keep processing header lines
}
}
{
if (c == end)
{ // no colon found!
// raise bad header error or simply ignore?
return NO; // keep processing header lines
}
}
key=[[NSString stringWithCString: (char *) buffer length: c-buffer] capitalizedString];
while(++c < end && (*c == ' ' || *c == '\t'))
; // skip spaces
; // skip spaces
val=[NSString stringWithCString: (char *) c length: end-c];
[_headers setObject: val forKey: [key lowercaseString]];
return NO; // not yet done
return NO; // not yet done
}
- (void) _processHeader: (unsigned char *) buffer length: (int) len
{ // next header fragment received
unsigned char *ptr, *end;
#if 0
#if 1
NSLog(@"received %d bytes", len);
#endif
if (len <= 0)
@ -534,147 +568,156 @@ static NSLock *regLock = nil;
#if 0
NSLog(@"stream: %@ handleEvent: %x for: %@", stream, event, self);
#endif
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 (stream == this->input)
{
#if 1
NSLog(@"receive error %@", [NSError _last]);
NSLog(@"input stream handleEvent: %x for: %@", event, self);
#endif
[this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"receive error" code: 0 userInfo: nil]];
[self _unschedule];
return;
}
if (_readingBody)
[this->client URLProtocol: self didLoadData: [NSData dataWithBytes: buffer length: len]]; // notify
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;
}
}
switch(event)
{
case NSStreamEventHasBytesAvailable:
{
unsigned char buffer[512];
int len;
len = [(NSInputStream *)stream read: buffer
maxLength: sizeof(buffer)];
if (len < 0)
{
#if 1
NSLog(@"receive error %@", [NSError _last]);
#endif
[this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"receive error" code: 0 userInfo: nil]];
[self _unschedule];
return;
}
if (_readingBody)
[this->client URLProtocol: self didLoadData: [NSData dataWithBytes: buffer length: len]]; // notify
else
[self _processHeader: buffer length: len];
return;
}
case NSStreamEventEndEncountered: // can this occur in parallel to NSStreamEventHasBytesAvailable???
{
#if 1
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 1
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
*/
switch(event)
{
case NSStreamEventOpenCompleted:
{
#if 0
NSLog(@"HTTP output stream opened");
#endif
msg=(unsigned char *) [[NSString stringWithFormat: @"%@ %@ HTTP/1.1\r\n",
[this->request HTTPMethod],
[[this->request URL] absoluteString]
] cString]; // FIXME: UTF8???
[(NSOutputStream *) stream write: msg maxLength: strlen((char *) msg)];
#if 1
NSLog(@"sent %s", msg);
#endif
_headerEnumerator=[[[this->request allHTTPHeaderFields] objectEnumerator] retain];
return;
}
case NSStreamEventHasSpaceAvailable:
{
// FIXME: should also send out relevant Cookies
if (_headerEnumerator)
{ // send next header
NSString *key;
key=[_headerEnumerator nextObject];
if (key)
{
#if 1
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 1
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;
}
}
{
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
*/
switch(event)
{
case NSStreamEventOpenCompleted:
{
#if 0
NSLog(@"HTTP output stream opened");
#endif
msg = (unsigned char *)[[NSString stringWithFormat:
@"%@ %@ HTTP/1.1\r\n",
[this->request HTTPMethod],
[[this->request URL] absoluteString]] UTF8String];
[(NSOutputStream *) stream write: msg
maxLength: strlen((char *) msg)];
#if 0
NSLog(@"sent %s", msg);
#endif
_headerEnumerator = [[[this->request allHTTPHeaderFields] keyEnumerator] retain];
return;
}
case NSStreamEventHasSpaceAvailable:
{
// FIXME: should also send out relevant Cookies
if (_headerEnumerator)
{ // send next header
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);
[this->client URLProtocol: self didFailWithError: [stream streamError]];
}

View file

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

View file

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

View file

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