Request timeout with Facebook API on iOS - objective-c

I have a bunch of URL requests that are sent out in batch and then a 'catcher' class that recieves the results. I'm doing this through facebook's graph API SDK so I don't have control of the actual NSURL. My problem is that I don't trigger the next result protocol until all requests have come back, but sometimes, especially over 3G, they don't always come back (especially when you have a few hundred). What's a good way to 'give up' on a given request? The way the actual request works, for those who aren't familiar with the FB api, is you initialize a facebook object and then request data from it, and you specify a return delegate. So, for each individual request I have a returnDelegate (the catcher), which I can initialize specific to each request.
UPDATE: Some code from FB-API
- (FBRequest*)requestWithMethodName:(NSString *)methodName
andParams:(NSMutableDictionary *)params
andHttpMethod:(NSString *)httpMethod
andDelegate:(id <FBRequestDelegate>)delegate {
NSString * fullURL = [kRestserverBaseURL stringByAppendingString:methodName];
return [self openUrl:fullURL
params:params
httpMethod:httpMethod
delegate:delegate];
}
- (FBRequest*)openUrl:(NSString *)url
params:(NSMutableDictionary *)params
httpMethod:(NSString *)httpMethod
delegate:(id<FBRequestDelegate>)delegate {
[params setValue:#"json" forKey:#"format"];
[params setValue:kSDK forKey:#"sdk"];
[params setValue:kSDKVersion forKey:#"sdk_version"];
if ([self isSessionValid]) {
[params setValue:self.accessToken forKey:#"access_token"];
}
[self extendAccessTokenIfNeeded];
FBRequest* _request = [FBRequest getRequestWithParams:params
httpMethod:httpMethod
delegate:delegate
requestURL:url];
[_requests addObject:_request];
[_request addObserver:self forKeyPath:requestFinishedKeyPath options:0 context:finishedContext];
[_request connect];
return _request;
}
- (void)connect {
if ([_delegate respondsToSelector:#selector(requestLoading:)]) {
[_delegate requestLoading:self];
}
NSString* url = [[self class] serializeURL:_url params:_params httpMethod:_httpMethod];
NSMutableURLRequest* request =
[NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:kTimeoutInterval];
[request setValue:kUserAgent forHTTPHeaderField:#"User-Agent"];
[request setHTTPMethod:self.httpMethod];
if ([self.httpMethod isEqualToString: #"POST"]) {
NSString* contentType = [NSString
stringWithFormat:#"multipart/form-data; boundary=%#", kStringBoundary];
[request setValue:contentType forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[self generatePostBody]];
}
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
self.state = kFBRequestStateLoading;
self.sessionDidExpire = NO;
}

As discussed above, you can implement your own timeout by instantiating an NSTimer which will signal a delegate object when a certain amount of time elapses.

Related

Cocoa: POST headers/parameters lost when accessing protected resources in a Django site using NSURLConnection

I am trying to access protected resources on a Django site using NSURLConnection , OAuth2 Bearer token and HTTPS. I receive a token, which I then attach either to a GET parameter, POST parameter or header. I can access those URL:s which respond to GET parameter. But when I try to access urls using POST, the server returns me a 403 with a custom error message saying there is no header/post parameter containing the token. I have tried several solutions and HTTP libraries. This method uses AFNetworking, I tried it. We even changed the authorization to accept an alternative header due to warnings that apple does not like the modifying of "Authorization" header. My current code looks like this: (scheme == #"https")
-(void) logOut {
NSString *pget = #"/api/logout/";
NSString *path = [pget stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *absolutePath = [NSString stringWithFormat:#"%#://%#%#", scheme, host, path];
NSURL *url = [NSURL URLWithString:absolutePath];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30.0];
NSString *authValue = [NSString stringWithFormat:#"Bearer %#", accessToken];
[urlRequest setValue:authValue forHTTPHeaderField:#"Authorization_Extra"];
[urlRequest setValue:#"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:#"content-type"];
[urlRequest setHTTPMethod: #"POST"];
/*
NSString *post = [NSString stringWithFormat:#"access_token_extra=%#", accessToken];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[postData length]];
[urlRequest setValue:postLength forHTTPHeaderField:#"Content-Length"];
[urlRequest setHTTPBody:postData];
*/
NSDictionary* headers = [urlRequest allHTTPHeaderFields];
NSLog(#"headers: %#",headers);
_originalRequest = urlRequest;
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self startImmediately:NO];
[connection start];
}
#pragma mark NSURLConnection Delegate Methods
- (NSURLRequest *)connection: (NSURLConnection *)connection
willSendRequest: (NSURLRequest *)request
redirectResponse: (NSURLResponse *)redirectResponse;
{
if (redirectResponse) {
// we don't use the new request built for us, except for the URL
NSURL *newURL = [request URL];
NSMutableURLRequest *newRequest = [_originalRequest mutableCopy];
[newRequest setURL: newURL];
NSLog(#"New Request headers: %#", [newRequest allHTTPHeaderFields]);
return newRequest;
} else {
return request;
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
NSLog(#"Received response statuscode: %ld", (long)[response statusCode]);
responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
return nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"connection finished:");
[_delegate handleData:responseData];
}
The _Delegate handleData parses the response, and the custom response is always that I am lacking either the header or post parameter needed for the Bearer token.
It seems that even though I am replacing the request with a mutable copy of the original on every redirect, the headers/parameters still get stripped by NSURLConnection. But why, and how, since I'm sending a copy of the original request every time and I verify by logging that they are there?

Can I pull content from a specific webpage in objective c?

I'm looking to create a simple iOS app that displays the current water level of a local lake. The water level is updated daily on a specific URL. Is it possible to pull content from a webpage using objective c?
Sure. Check out the URL Loading System Programming Guide. From that link:
The URL loading system provides support for accessing resources using the following protocols:
File Transfer Protocol (ftp://)
Hypertext Transfer Protocol (http://)
Secure 128-bit Hypertext Transfer Protocol (https://)
Local file URLs (file:///)
Absolutely! Use the NSURLConnection object. Use something like the function below, just pass an empty string for 'data' and then parse the HTML returned to find the value you're looking for.
-(void)sendData:(NSString*)data toServer:(NSString*)url{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSData *postData = [data dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%d",[postData length]];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setURL:[NSURL URLWithString:url]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Current-Type"];
[request setHTTPBody:postData];
NSURLConnection *conn = [[NSURLConnection alloc]initWithRequest:request delegate:self];
if(conn){
//Connection successful
}
else{
//Connection Failed
}
[conn release];
}
Easier way yet using threading:
- (void)viewDidLoad
{
[self contentsOfWebPage:[NSURL URLWithString:#"http://google.com"] callback:^(NSString *contents) {
NSLog(#"Contents of webpage => %#", contents);
}];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void) contentsOfWebPage:(NSURL *) _url callback:(void (^) (NSString *contents)) _callback {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:_url];
dispatch_sync(dispatch_get_main_queue(), ^{
_callback([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
});
});
}

How to pass additional parameters to ASIHTTPRequest didFinishSelector selector

Im building api for our rest api using ASIHTTPRequest.
Api wraps all requests to its own methods.
- (void) foo:(id)caller
andSuccessCallback:(SEL)successCallback
andFailureCallback:(SEL)failureCallback {
_currentCaller=caller;
_currentSuccessCallback=successCallback;
_currentFailureCallback=failureCallback;
NSString *urlString = #"http://localhost/foo";
NSURL *url= [NSURL URLWithString: urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:#selector(fooSuccess:)];
[request startAsynchronous];
}
- (void) fooSuccess:(ASIHTTPRequest *)request {
if (statusCode >=400) {
if ([_currentCaller respondsToSelector:_currentFailureCallback])
[_currentCaller performSelector:_currentFailureCallback withObject:statusCode withObject:message];
} else {
if ([_currentCaller respondsToSelector:_currentSuccessCallback])
[_currentCaller performSelector:_currentSuccessCallback];
}
[request release];
}
Currently to pass callback selector I use class private variables _currentCaller _currentSuccessCallback _currentFailureCallback=failureCallback
Can i pass them to fooSuccess callback directly?
You can pass objects by populating a NSDictionary and setting it as the request's userInfo property.

NSURLConnection POST also calls GET of same URL

I have a NSURLConnection which is a post to the server, but I expect it to return some small data, whether it was successful or not.
-(void)submitPost:(NSString *)xml
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[service generateURL]];
NSString *result = (NSString *) CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)xml, NULL, CFSTR("?=&+"), kCFStringEncodingUTF8);
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[result dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPMethod:#"POST"];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:request delegate:self];
if(theConnection)
{
NSLog(#"Connection success");
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[theConnection retain];
failed = NO;
}
else
{
NSLog(#"Connection failed");
}
}
The problem is, not only does it send a post the URL, it also sends a GET, and the GET response is returned as the data... I'm a bit confused. I checked my wireshark output, and it's definitely making both a post and a get.
What do you guys think?
Does the URL respond to a POST with redirect? You can implement the NSURLConnection delegate method connection:willSendRequest:redirectResponse: to see if that's the case (and to cancel an unwanted redirect).

Objective-C Asynchronous Web Request with Cookies

I am writing a program in Objective-C and I need to make web requests to web server, but asynchronously and I am fairly new on mac, I am very good at windows technologies, but I need to know that if I use NSOperation (introduced in 10.5, i am assuming that it will not run in 10.4 MAC?), or if it was implemented such that it utilizes system threading which will be available on 10.4?
Or I should create a new thread and create a new runloop, also how to use cookies etc, if anyone can give me one small example, that will be of great help. I want this sample to run on mac 10.4 too if possible.
There's a good example of using NSURLRequest and NSHTTPCookies to do a full web application example of logging into a website, storing the SessionID cookie and resubmitting it in future requests.
NSURLConnection, NSHTTPCookie
By logix812:
NSHTTPURLResponse * response;
NSError * error;
NSMutableURLRequest * request;
request = [[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://temp/gomh/authenticate.py?setCookie=1"]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60] autorelease];
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"RESPONSE HEADERS: \n%#", [response allHeaderFields]);
// If you want to get all of the cookies:
NSArray * all = [NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:[NSURL URLWithString:#"http://temp"]];
NSLog(#"How many Cookies: %d", all.count);
// Store the cookies:
// NSHTTPCookieStorage is a Singleton.
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:all forURL:[NSURL URLWithString:#"http://temp"] mainDocumentURL:nil];
// Now we can print all of the cookies we have:
for (NSHTTPCookie *cookie in all)
NSLog(#"Name: %# : Value: %#, Expires: %#", cookie.name, cookie.value, cookie.expiresDate);
// Now lets go back the other way. We want the server to know we have some cookies available:
// this availableCookies array is going to be the same as the 'all' array above. We could
// have just used the 'all' array, but this shows you how to get the cookies back from the singleton.
NSArray * availableCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString:#"http://temp"]];
NSDictionary * headers = [NSHTTPCookie requestHeaderFieldsWithCookies:availableCookies];
// we are just recycling the original request
[request setAllHTTPHeaderFields:headers];
request.URL = [NSURL URLWithString:#"http://temp/gomh/authenticate.py"];
error = nil;
response = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"The server saw:\n%#", [[[NSString alloc] initWithData:data encoding: NSASCIIStringEncoding] autorelease]);
For asynchronous requests, you need to use NSURLConnection.
For cookies, see NSHTTPCookie and NSHTTPCookieStorage.
UPDATE:
The code below is real, working code from one of my applications. responseData is defined as NSMutableData* in the class interface.
- (void)load {
NSURL *myURL = [NSURL URLWithString:#"http://stackoverflow.com/"];
NSURLRequest *request = [NSURLRequest requestWithURL:myURL
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[responseData release];
[connection release];
// Show error message
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Use responseData
[responseData release];
[connection release];
}
I am able to fetch cookie in such way:
NSArray* arr = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString: #"http://google.com" ]];
This one works fine for asynchronous request as well.