#import "DAClient.h" static const CFOptionFlags DAClientNetworkEvents = kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred; static void ReadStreamClientCallBack(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo) { // Pass off to the object to handle. [((DAClient *)clientCallBackInfo) handleNetworkEvent:type]; } @implementation DAClient - (void) informDelegateRequestFinished:(NSData *)data { if ([self delegate] == nil) { return; } if ([[self delegate] respondsToSelector:@selector(requestDidFinish:)]) { [[self delegate] requestDidFinish:data]; } else { [NSException raise:NSInternalInconsistencyException format:@"Delegate doesn't respond to requestDidFinish:"]; } } - (BOOL) fetchUrl:(NSURL *)url withMethod:(int)method withParameters:(NSDictionary *)params withDelegate:(id)delegate { CFHTTPMessageRef request; CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL}; NSMutableString *paramString = nil; // Don't fetch if there is a fetch in progress. if (_stream) { return NO; } // Make sure we have a url if (url == nil) { return NO; } if (_data != nil) { [_data release]; } _data = [[NSMutableData alloc] initWithCapacity:2048]; [self setDelegate:delegate]; // build a parameter string if (params != nil) { paramString = [NSMutableString stringWithCapacity:50]; id key; BOOL firstParam = YES; NSEnumerator *enumerator = [params keyEnumerator]; while ((key = [enumerator nextObject])) { id value = [params objectForKey:key]; if ([key isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) { if (firstParam) { firstParam = NO; } else { [paramString appendString:@"&"]; } NSString *paramKey = (NSString *)CFURLCreateStringByAddingPercentEscapes( NULL, (CFStringRef)key, NULL, NULL, kCFStringEncodingUTF8); NSString *paramValue = (NSString *)CFURLCreateStringByAddingPercentEscapes( NULL, (CFStringRef)value, NULL, NULL, kCFStringEncodingUTF8); [paramString appendString:paramKey]; [paramString appendString:@"="]; [paramString appendString:paramValue]; [paramKey release]; [paramValue release]; } } } // Create a new HTTP request. if (method == DAClientMethodGET) { // for GET requests, append parameter list to url NSURL *tmpURL; if (paramString != nil) { // This is buggy for certain URLs. // It only really works with urls like "http://foo.com/" // or "http://foo.com/file.pl" NSMutableString *tmpS = [NSMutableString stringWithString:[url absoluteString]]; [tmpS appendString:@"?"]; [tmpS appendString:paramString]; tmpURL = [NSURL URLWithString:tmpS]; } else { tmpURL = url; } request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), (CFURLRef)tmpURL, kCFHTTPVersion1_1); } else if (method == DAClientMethodPOST) { request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("POST"), (CFURLRef)url, kCFHTTPVersion1_1); CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Content-Type"), CFSTR("application/x-www-form-urlencoded")); // Set the parameters in the body of POST requests if (paramString && request) { NSData *data = [paramString dataUsingEncoding:NSUTF8StringEncoding]; CFHTTPMessageSetBody(request, (CFDataRef)data); } } if (!request) { return NO; } // Create the stream for the request. _stream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request); // Release the request. The fetch should've retained it if it // is performing the fetch. CFRelease(request); // Make sure it succeeded. if (!_stream) { return NO; } // Set the client. Make it call ReadStreamClientCallBack() if (!CFReadStreamSetClient(_stream, DAClientNetworkEvents, ReadStreamClientCallBack, &ctxt)) { CFRelease(_stream); _stream = NULL; return NO; } // Schedule the stream CFReadStreamScheduleWithRunLoop(_stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); // Start the HTTP connection if (!CFReadStreamOpen(_stream)) { CFReadStreamSetClient(_stream, 0, NULL, NULL); CFReadStreamUnscheduleFromRunLoop(_stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFRelease(_stream); _stream = NULL; return NO; } return YES; } - (void) cancelPending { CFReadStreamSetClient(_stream, 0, NULL, NULL); CFReadStreamUnscheduleFromRunLoop(_stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFReadStreamClose(_stream); CFRelease(_stream); _stream = NULL; } - (void) handleNetworkEvent:(CFStreamEventType)type { // Dispatch the stream events. switch (type) { case kCFStreamEventHasBytesAvailable: [self handleBytesAvailable]; break; case kCFStreamEventEndEncountered: [self handleStreamComplete]; break; case kCFStreamEventErrorOccurred: [self handleStreamError]; break; default: break; } } - (void) handleBytesAvailable { UInt8 buffer[2048]; CFIndex bytesRead = CFReadStreamRead(_stream, buffer, sizeof(buffer)); // Less than zero is an error if (bytesRead < 0) { [self handleStreamError]; // If zero bytes were read, wait for the EOF to come. } else if (bytesRead) { // This would not work for binary data! Build a string to add // to the results. [_data appendBytes:(void *)buffer length:(unsigned)bytesRead]; } } - (void) handleStreamComplete { // Don't need the stream any more, and indicate complete. CFReadStreamSetClient(_stream, 0, NULL, NULL); CFReadStreamUnscheduleFromRunLoop(_stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFReadStreamClose(_stream); CFRelease(_stream); _stream = NULL; [self informDelegateRequestFinished:(NSData *)_data]; } - (void) handleStreamError { CFStreamError error = CFReadStreamGetError(_stream); if (error.error != 0) { if (error.domain == kCFStreamErrorDomainPOSIX) { NSLog(@"posix errno: %d", error.error); } else if (error.domain == kCFStreamErrorDomainMacOSStatus) { OSStatus macError = (OSStatus)error.error; NSLog(@"os error: %d", macError); } else if (error.domain == kCFStreamErrorDomainHTTP) { NSLog(@"http error domain"); } else if (error.domain == kCFStreamErrorDomainMach) { NSLog(@"mach error domain"); } else if (error.domain == kCFStreamErrorDomainNetDB) { NSLog(@"netdb error domain"); } else if (error.domain == kCFStreamErrorDomainCustom) { NSLog(@"custom error domain"); } else if (error.domain == kCFStreamErrorDomainSystemConfiguration) { NSLog(@"system configuration error domain"); } NSLog(@"error %d domain %d", error.error, error.domain); } CFReadStreamSetClient(_stream, 0, NULL, NULL); CFReadStreamUnscheduleFromRunLoop(_stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFReadStreamClose(_stream); CFRelease(_stream); _stream = NULL; [self informDelegateRequestFinished:nil]; } - (id) delegate { return _delegate; } - (void) setDelegate:(id)newDelegate { // Note that we DO NOT retain this. This is to avoid circular refs. _delegate = newDelegate; } - (void) dealloc { [_data release]; [super dealloc]; } @end