HTTP server works in Cocoa application but not test case -- run loop issue? - objective-c

I'm trying to add a GHUnit test case to this SimpleHTTPServer example. The example include a Cocoa application that works fine for me. But I can't duplicate the behavior in a test case.
Here is the test class:
#import <GHUnit/GHUnit.h>
#import "SimpleHTTPServer.h"
#interface ServerTest : GHTestCase
{
SimpleHTTPServer *server;
}
#end
#implementation ServerTest
-(void)setUpClass
{
[[NSRunLoop currentRunLoop] run];
}
- (NSString*)requestToURL:(NSString*)urlString error:(NSError**)error
{
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:1];
NSURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:error];
NSString *page = nil;
if (error == nil)
{
NSStringEncoding responseEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)[response textEncodingName]));
page = [[NSString alloc] initWithData:data encoding:responseEncoding];
[page autorelease];
}
return page;
}
- (void)testPortReuse
{
unsigned int port = 50001;
NSError *error = nil;
NSString *path, *url;
server = [[SimpleHTTPServer alloc] initWithTCPPort:port delegate:self];
sleep(10);
path = #"/x/y/z";
url = [NSString stringWithFormat:#"http://localhost:%u%#", port, path];
[self requestToURL:url error:&error];
GHAssertNil(error, #"%# : %#", url, error);
[server release];
}
- (void)processURL:(NSURL *)path connection:(SimpleHTTPConnection *)connection
{
NSLog(#"processURL");
}
- (void)stopProcessing
{
NSLog(#"stopProcessing");
}
#end
I've tried sending requests via NSURLRequest and also (during the sleep) via a web browser. The delegate methods -processURL and -stopProcessing are never called. The problem seems to be that [fileHandle acceptConnectionInBackgroundAndNotify] in SimpleHTTPServer -initWithTCPPort:delegate: is not causing any NSFileHandleConnectionAcceptedNotifications to reach the NSNotificationCenter -- so I suspect a problem involving run loops.
The problem seems to be with the NSFileHandle, not the NSNotificationCenter, because when [nc postNotificationName:NSFileHandleConnectionAcceptedNotification object:nil] is added to the end of initWithTCPPort:delegate:, the NSNotificationCenter does get the notification.

if (error == nil)
That should be:
if (data != nil)
error here is the passed-in pointer to an NSError* - it will only be nil if the caller passed nil instead of a reference to an NSError* object, which isn't what your -testPortReuse method does.
It would also be incorrect to dereference it (as in if (*error == nil)), because error arguments are not guaranteed to be set to nil upon error. The return value indicates an error condition, and the value returned in the error argument is only meaningful or reliable if there is an error. Always check the return value to determine if an error happened, then check the error parameter for details only if something did in fact go wrong.
In other words, as it's written above, your -requestToURL:error: method is incapable of handling success. Much like Charlie Sheen. :-)

Related

How can I get NSURLSession to process completionHandler when running as a command-line tool

I am somewhat new to Objective-C. I have some PERL scripts that download files from a site that requires client certificate authentication. I would like to port those scripts from PERL to Objective-C command line tools that I can then run from Terminal. Based on my research I have concluded that NSURLSession should work for what I need to do. However, I have been unable to get NSURLSession to work in my command line tool. It seems to build fine with no errors, but does not return anything from the completionHandler. Further, I have put this same code into a Mac OS X App tool and it seems to work fine.
Here is my main.m file:
#import <Foundation/Foundation.h>
#import "RBC_ConnectDelegate.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
// insert code here...
NSURL *myURL =[NSURL URLWithString:#"http://google.com"];
//[[[RBC_Connect alloc] init] connectGetURLSynch];
RBC_ConnectDelegate *myConnect = [[RBC_ConnectDelegate alloc] init];
[myConnect GetURL2: myURL];
}
return 0;
}
Here is my Implementation File:
#import <Foundation/Foundation.h>
#interface RBC_ConnectDelegate : NSObject
- (void)GetURL2:(NSURL *)myURL;
#property(nonatomic,assign) NSMutableData *receivedData;
//<==== note use assign, not retain
//do not release receivedData in a the dealloc method!
#end
And here is my implementation file:
#import "RBC_ConnectDelegate.h"
#implementation RBC_ConnectDelegate
- (void)GetURL2:(NSURL *)myURL2{
//create semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// Create the request.
NSLog(#"Creating Request");
NSURLRequest *theRequest =
[NSURLRequest requestWithURL:myURL2
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:10.0];
NSLog(#"Creating Session");
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSLog(#"Initializing Data Task");
NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithRequest:theRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"CompletionHandler");
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger myStatusCode = [(NSHTTPURLResponse *) response statusCode];
NSLog(#"Status Code: %ld", (long)myStatusCode);
}
if(error == nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Data = %#",text);
}
else
{
NSLog(#"Error");
}
dispatch_semaphore_signal(semaphore);
}];
NSLog(#"Resuming Data Task");
[dataTask resume];
}
#end
As you can see, I am trying to get something very simple working here first, with the idea that I can then build on it. Everything I have looked at suggests this may be related to the fact that NSURLSession runs asynchronously but I have been unable to find a solution that speaks specifically to how to address this issue when building a command-line tool. Any direction anyone can provide would be appreciated.
cheers,
NSOperationQueue's reference says that for +mainQueue, the main thread's run loop controls the execution of operations, so you need a run loop. Add this to the end of your main:
while (1)
{
SInt32 res = 0;
#autoreleasepool
{
res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, DBL_MAX, false);
}
if (kCFRunLoopRunStopped == res || kCFRunLoopRunFinished == res)
break;
}
To call -GetURL2:, use -[NSOperationQueue addOperation:] or dispatch_async. You can stop execution with CFRunLoopStop. I tested with GCD instead of NSOperations but this should work anyway.
Also, you need to call dispatch_semaphore_wait if you want to synchronize after the URL session is complete.

Data transfer between NSOperations

I would like to obtain the following: I have two NSOperations in a NSOperationQueue. The firs is a download from a website (gets some json data) the next is parsing that data. This are dependent operations.
I don't understand how to link them together. If they are both allocated and in the queue, how do I transfer the json string to the operation that parses it? Is it a problem if this queue is inside another NSOperationQueue that executes an NSOperation that consists of the two mentioned previously?
All I could find is transfers of data to a delegate on the main thread (performSelectorOnMainThread), but I need all this operations to execute in the background.
Thanks.
Code:
NSDownload : NSOperation
- (instancetype)initWithURLString:(NSString *)urlString andDelegate:(id<JSONDataDelegate>)delegate
{
self = [super init];
if (self) {
_urlStr = urlString;
_delegate = delegate; /// this needs to be a NSOPeration
_receivedData = [NSMutableData dataWithCapacity:256];
}
return self;
}
#pragma mark - OVERRIDE
- (void)main
{
#autoreleasepool {
if (self.isCancelled) {
return;
}
NSURL *url = [NSURL URLWithString:self.urlStr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
self.urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
}
#pragma mark - NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if (self.isCancelled) {
[connection cancel];
self.receivedData = nil;
return;
}
[self.receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (self.isCancelled) {
self.receivedData = nil;
return;
}
// return data to the delegate
NSDictionary *responseDict = #{JSON_REQUESTED_URL : self.urlStr,
JSON_RECEIVED_RESPONSE : self.receivedData};
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(didReceiveJSONResponse:) withObject:responseDict waitUntilDone:NO]; // ok to uses performSelector as this data is not for use on the main thread ???
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// return error to the delegate
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(didFailToReceiveDataWithError:) withObject:error waitUntilDone:NO];
}
#user1028028:
Use the following approach.
1) Maintain the operation queue reference that you are using to add DownloadOperation.
2) In connectionDidFinishLoading method, create ParseOperation instance, set the json data and add it to operation queue. Maintain ParseOperation strong reference variable in DownloadOperation and handling of cancelling of parsing operation through DownloadOperation interface.
3) After completed parsing call the UI functionality in main thread.
I hope this helps.
As lucianomarisi notes, it would usually be best to just have the first operation generate the second operation. This is usually simpler to manage. Operation dependencies aren't really that common in my experience.
That said, it's of course possible to pass data between operations. For instance, you could create a datasource property on the second operation. That would be the object to ask for its data; that object would be the first operation. This approach may require locking, though.
You can also create a nextOp property on the first operation. When it completes, it would call setData: on the second operation before exiting. You probably wouldn't need locking for this, but you might. In most cases it would be better for the first operation to just schedule the nextOp at this point (which again looks like lucianomarisi's answer).
The point is that an operation is just an object. It can have any methods and properties you want on it. And you can pass one operation to another.
Keep in mind that since an operation runs in the background, there's no reason you need to use the asynchronous interface to NSURLConnection. The synchronous API (sendSynchronousRequest:returningResponse:error: is fine for this, and much simpler to code. You could even use a trivial NSBlockOperation. Alternately, you can use the asynchronous NSURLConnection interface, but then you really don't need an NSOperation.
I also notice:
_receivedData = [NSMutableData dataWithCapacity:256];
Is it really such a small piece of JSON data? It's hard to believe that this complexity is worth it to move such a small parsing operation to the background.
(As a side note, unless you know precisely the size of the memory, there's not usually much benefit to specifying a capacity manually. Even then it's not always clear that it's a benefit. I believe NSURLConnection is using dispatch data under the covers now, so you're actually requesting a memory block that will never be used. Of course Cocoa also won't allocate it because it optimizes that out... the point is that you might as well just use [NSMutableData data]. Cocoa is quite smart about these kinds of things; you generally can only get in the way of its optimizations.)
As Rob said, unless you have any particular reason to use operations use the synchronized call. Then perform the selector on MainThread or on any other thread you need. Unless you want to separate the retrieval and parsing in separate operations or thread (explicitly).
Here is the code I was using for json retrieval and parsing:
-(BOOL) loadWithURL:(NSString*) url params: (NSDictionary*) params andOutElements:(NSDictionary*) jElements
{
NSError *reqError = nil;
NSString* urlStr = #"";//#"http://";
urlStr = [urlStr stringByAppendingString:url];
NSURL* nsURL = [NSURL URLWithString:urlStr];
//Private API to bypass certificate ERROR Use only for DEBUG
//[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[nsURL host]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: nsURL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[request setHTTPMethod:#"POST"];
NSString *postString = #"";
if(params!=nil) {
NSEnumerator* enumerator = params.keyEnumerator;
NSString* aKey = nil;
while ( (aKey = [enumerator nextObject]) != nil) {
NSString* value = [params objectForKey:aKey];
//Use our own encoded implementation instead of above Apple one due to failing to encode '&'
NSString* escapedUrlString =[value stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
//Required to Fix Apple bug with not encoding the '&' to %26
escapedUrlString = [escapedUrlString stringByReplacingOccurrencesOfString: #"&" withString:#"%26"];
//this is custom append method. Please implement it for you -> the result should be 'key=value' or '&keyNotFirst=value'
postString = [self appendCGIPairs:postString key:aKey value:escapedUrlString isFirst:false];
}
}
//************** Use custom enconding instead !!!! Error !!!!! **************
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&reqError];
if(reqError!=nil) {
NSLog(#"SP Error %#", reqError);
return NO;
}
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
//Handles Server Errors during execution of the web service that handles the call.
if([json_string hasPrefix:#"ERROR"] == YES){
NSLog(#"SP Error %#", lastError);
return NO;
}
//Very Careful!!!!!! Will stop for any reason.!!!!!!
//Handles errors from IIS Server that serves teh request.
NSRange range = [json_string rangeOfString:#"Runtime Error"];
if(range.location != NSNotFound) {
NSLog(#"SP Error %#", lastError);
return NO;
}
//Do the parsing
jElements = [[parser objectWithString:json_string error:nil] copy];
if([parser error] == nil) {
NSLog(#"Parsing completed");
} else {
jElements = nil;
NSLog(#"Json Parser error: %#", parser.error);
NSLog(#"Json string: %#", json_string);
return NO;
}
//Parsed JSON will be on jElements
return YES;
}

Why i get timeout erros when page is not available

I use NSString method initWithContentsOfURL:usedEncoding:error: to get content of some page. I notice, when page that i try to access is not exist this method is executed long time and then fail with timeout error. I tried to use NSURLRequest and NSURLConnection classes for the same purposes, but get the same result - execution long time and then timeout error.
When i try to open the same page in browser, i get response more quickly and it returns page is not available error.
It looks like cocoa methods don't do a dns resolution for page name, or they have longer timeout for that operation.
So my question, does cocoa method that i use do dns resolve? How to do that if they didn't?
Samples of code i use:
NSURL* url = [NSURL URLWithString:#"http://unexisting.domain.local"];
NSError* err = nil;
NSString* content = [NSString stringWithContentsOfURL:url usedEncoding:nil error:&err];
if (err) {
NSLog(#"error: %#", err);
} else {
NSLog(#"content: %#", content);
}
NSURL* url = [NSURL URLWithString:#"http://unexisting.domain.local"];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
NSURLResponse* response = nil;
NSError* err = nil;
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&err];
if (err) {
NSLog(#"error: %#", err);
} else {
NSString* content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"content: %#", content);
}
Thanks!
Gene M. answered how to do that with using of SCNetworkReachability. Here is sample code:
bool success = false;
const char *host_name = [#"stackoverflow.com"
cStringUsingEncoding:NSASCIIStringEncoding];
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL,
host_name);
SCNetworkReachabilityFlags flags;
success = SCNetworkReachabilityGetFlags(reachability, &flags);
bool isAvailable = success && (flags & kSCNetworkFlagsReachable) &&
!(flags & kSCNetworkFlagsConnectionRequired);
if (isAvailable) {
NSLog(#"Host is reachable: %d", flags);
}else{
NSLog(#"Host is unreachable");
}
It's definitely good practice to monitor the reachability (device connectivity) and react to it as noted above.
You can also implement the NSURLConnectionDelegate protocol and its methods such as
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"**ERROR** %#", error);
// Respond to error
}
If you're not connected you should get an NSURLConnection error code of 999 and NSURLErrorCancelled = -999 and/or a NSURLErrorNotConnectedToInternet = -1009 if you're not connected, etc. At least you'd have a report back of what's going on.
Docs:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Constants/Reference/reference.html

Constantly calling a method

So I have a method that checks for internet connection, but only during the -(id):init method. Can I set it up so that it constantly checks for connection? If it helps, here is the code:
- (id) checkConnected
{
NSError *error = nil;
NSString *URLString = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.google.com"] encoding:NSASCIIStringEncoding error:&error];
if (URLString != NULL)
{
connected = YES;
}
else connected = NO;
if(connected == YES)
NSLog(#"Connected");
else if (connected == NO)
NSLog(#"NotConnected");
return self;
}
While Reachability is a good first-pass check as others have suggested, it only tests the negative case: is it impossible to make a connection? If a firewall is blocking you, or the remote server is down, or any of a thousand other things happens, Reachability might tell you a system is in principle reachable (i.e. you have a network connection and the host if routeable) but the host is not in fact reachable.
So for some applications what you are asking is not unreasonable. The thing you have to be careful about is not to block your main thread with constant tests. Here is some code that will repeatedly run tests in the background:
NSURL *url = [NSURL URLWithString:#"http://www.google.com"];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
__block NSHTTPURLResponse *response = nil;
__block NSError *error = nil;
dispatch_queue_t netQueue = dispatch_queue_create("com.mycompany.netQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(netQueue, ^{
while (! [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error]) {
NSLog(#"Connection failed.");
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Connection succeeded");
});
});
dispatch_release(netQueue);
Where "Connection succeeded" is logged you could instead write some main thread code that runs when a connection is successful. Note that I am passing in *response and *error from outside the block so they too will be available on your main thread inside or outside the block (assuming you keep them in scope) for your use.
You may want to throttle (i.e. just not use while()), but this is an implementation detail. Using NSTimer() as Richard suggested would work.
Finally, even with this code you still need to handle a potential failure of a subsequent connection. Just because it worked once doesn't mean the connection is available a millisecond later.

Cannot get Length of a NSString - unrecognized selector sent to instance

What I'm trying to get is to search for the Anime Titile's ID, compare the length and perform some action afterwards. Here is what I get in the debugger:
2010-08-09 14:30:48.818 MAL Updater OS X[37415:a0f] Detected : Amagami SS - 06
2010-08-09 14:30:48.821 MAL Updater OS X[37415:a0f] http://mal-api.com/anime/search?q=Amagami%20SS
2010-08-09 14:30:49.635 MAL Updater OS X[37415:a0f] 8676
2010-08-09 14:30:49.636 MAL Updater OS X[37415:a0f] -[NSCFNumber length]: unrecognized selector sent to instance 0x384aa40
2010-08-09 14:30:49.637 MAL Updater OS X[37415:a0f] -[NSCFNumber length]: unrecognized selector sent to instance 0x384aa40
The code in question:
if ([self detectmedia] == 1) { // Detects Media from MPlayer via LSOF
NSLog(#"Detected : %# - %#", DetectedTitle, DetectedEpisode);
NSString * AniID = [self searchanime]; // Perform a Search Operation and Returns the ID of the time from JSON
NSLog(#"%#",AniID);
if (AniID.length > 0) { // Compare the length of AniID to make sure it contains a ID
// Other Action here
}
//Release Detected Title and Episode
[DetectedTitle release];
[DetectedEpisode release];
}
SearchAnime method:
-(NSString *)searchanime{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//Escape Search Term
NSString * searchterm = (NSString *)CFURLCreateStringByAddingPercentEscapes(
NULL,
(CFStringRef)DetectedTitle,
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8 );
//Set Search API
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://mal-api.com/anime/search?q=%#",searchterm]];
NSLog(#"%#",[NSString stringWithFormat:#"http://mal-api.com/anime/search?q=%#",searchterm]);
//Release searchterm
[searchterm release];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
//Ignore Cookies
[request setUseCookiePersistence:NO];
//Set Token
[request addRequestHeader:#"Authorization" value:[NSString stringWithFormat:#"Basic %#",[defaults objectForKey:#"Base64Token"]]];
//Perform Search
[request startSynchronous];
// Get Status Code
int statusCode = [request responseStatusCode];
NSString *response = [request responseString];
if (statusCode == 200 ) {
return [self RegExSearchTitle:response]; // Returns ID as NSString
}
else {
return #"";
}
}
RegExSearchTitle
-(NSString *)RegExSearchTitle:(NSString *)ResponseData {
OGRegularExpressionMatch *match;
OGRegularExpression *regex;
//Set Detected Anime Title
regex = [OGRegularExpression regularExpressionWithString:DetectedTitle];
NSEnumerator *enumerator;
// Initalize JSON parser
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSArray *searchdata = [parser objectWithString:ResponseData error:nil];
for (id obj in searchdata) {
// Look in every RegEx Entry until the extact title is found.
enumerator = [regex matchEnumeratorInString:[obj objectForKey:#"title"]];
while ((match = [enumerator nextObject]) != nil) {
// Return the AniID for the matched title
return [obj objectForKey:#"id"];
}
}
// Nothing Found, return nothing
return #"";
}
This behavior is unusual because I have compared the NSString's length in the past and it never failed on me. I am wondering, what is causing the problem?
The declared return type of RegExSearchTitle is NSString *, but that doesn’t force the returned object to actually be an NSString. The "id" element of obj (from the JSON) is a number, so an NSNumber is being returned. The compiler can’t warn you about this because it doesn’t know what classes will be found in a collection.
There are other bugs in the code. Having an unconditional return in a while statement in a for statement does not make sense.
On a side note, by convention Objective-C method names start with a lowercase letter.
Well, it's because you assigned an NSNumber to AniID, not an NSString. NSNumber doesn't have a length method.