Using asihttprequest to fetch data based on http status code - ios

I am trying to connect my iOS app to a GET data from my web service everytime something changes. My current implementation is to use NSTimer and do a ASIHttpRequest but I don't like that polling implementation. Is there a better way to do it?
I am considering starting the request and lets it keep trying until the web service status code turns to OK (status 200) or perhaps a status code of "modified". How would I do this in a view controller?
Here is what I have so far:
self.asiRequest = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:self.barcodeUrl]];
[self.asiRequest setDelegate:self];
[self.asiRequest startAsynchronous];
[self.asiRequest addRequestHeader:#"Accept" value:#"application/json"];
[self.asiRequest addRequestHeader:#"Content-Type" value:#"application/json"];
int statusCode = [self.asiRequest responseStatusCode];
I know that if the request is successful, the delegate method - (void)requestFinished:(ASIHTTPRequest *)request
I guess my question is, how do I keep an open ASIHttpRequest such that when the web service returns "modified", it calls a GET request and then keep an open connection again? This has to be asynchronous and not running on a UI thread as I don't want to keep my app hanging.
Thanks much!

The easiest way to do this without having to do much on the client-side is to get your web service to send a Location: header through when it returns "modified", pointing to the URL you want to GET with the new content. If the URL to monitor is different depending on e.g. user ID or the area of the app you are in, make a monitor script where you can pass in GET params to configure what exact URL should be returned to you when "modified" is returned.
The ASIHTTPRequest will then automatically GET the content at the new Location: and you can reschedule it in requestFinished: to the monitor URL again, knowing that the response (if not empty/"Not Modified") will ALWAYS be new data since the redirect to new data happens behind-the-scenes.

Related

NSURLSession with custom authentication challenge?

Our application makes use of RESTful service calls using NSURLSession. The calls themselves are routed through a reverse proxy, to aide in session management, security, etc. on the backend. The only problem we're having is related to authentication. When a user attempts to access a protected resource -- either through a browser or a REST call -- and they are not authenticated, the reverse proxy displays an HTML login page.
The problem is, we'd like to leverage NSURLSession's ability to handle authentication challenges automatically. However, NSURLSession is unable to recognize that we're getting back an authentication challenge, because no HTTP 401 or other error code is being returned to the client. The reverse proxy sends back a 200, because the HTML was delivered successfully. We're able to recognize that it is the login page by inspecting the HTML within the client, but we'd like to be able to handle the authentication using a custom NSURLAuthenticationChallenge, if at all possible, so that NSURLSession can retry requests after authentication is successful.
Is there any way for us to recognize that we're getting back a login page within our completionHandler and tell NSURLSession that we're really getting back an authentication challenge? Or does NSURLSession require that we receive back an HTTP error code (401, etc.)
A couple of possibilities come to mind.
If you're using the delegate rendition of NSURLSession, you might be able to detect the redirect to the authentication failure page via NSURLSessionTaskDelegate method willPerformHTTPRedirection.
You can probably also detect the redirect by examining the task's currentRequest.URL and see if a redirect happened there, too. As the documentation says, currentRequest "is typically the same as the initial request (originalRequest) except when the server has responded to the initial request with a redirect to a different URL."
Assuming that the RESTful service generally would not be returning HTML, you can look at the NSURLResponse, confirm that it's really a NSHTTPURLResponse subclass, and then look at allHeaderFields to confirm whether text/html appears in the Content-Type of the response. This obviously only works if the authentication page returns a Content-Type that includes text/html and the rest of your responses don't (e.g. they're application/json or something like that).
Anyway, that might look like:
NSString *contentType = [self headerValueForKey:#"Content-Type" response:response];
if ([contentType rangeOfString:#"text/html"].location != NSNotFound) {
// handle it here
}
Where, headerValueForKey might be defined as follows:
- (NSString *)headerValueForKey:(NSString *)searchKey response:(NSURLResponse *)response
{
if (![response isKindOfClass:[NSHTTPURLResponse class]])
return nil;
NSDictionary *headers = [(NSHTTPURLResponse *) response allHeaderFields];
for (NSString *key in headers) {
if ([searchKey caseInsensitiveCompare:key] == NSOrderedSame) {
return headers[key];
}
};
return nil;
}
At worst, you could detect the HTML response and parse it using something like HPPLE and programmatically detect the authentication HTML response.
See Wenderlich's article How to Parse HTML on iOS.
Frankly, though, I would prefer to see a REST response to report the authentication error with a REST response (or a proper authentication failure challenge or a non 200 response) rather than a redirect to a HTML page. If you have an opportunity to change the server response, that would be my personal preference.
Authentication challenges are triggered by the WWW-Authenticate header in a 40x response.
From the documentation :
The URL loading system classes do not call their delegates to handle request challenges unless the server response contains a WWW-Authenticate header. Other authentication types, such as proxy authentication and TLS trust validation do not require this header.
Unfortunately the proxy authentication referred to above does not apply in your reverse proxy/accelerator scenario.
Have you tried simply setting the username and password to the authorization header of NSMutableURLRequest as such.
NSString *authorizationString = [[[NSString stringWithFormat:#"%#:%#",user, password] dataUsingEncoding:NSUTF8StringEncoding] base64Encoding];
[yourRequest setValue:[NSString stringWithFormat:#"Basic %#", authorizationString] forHTTPHeaderField:#"Authorization"];
This should work.
You could try using an HTML Parser library (like https://github.com/nolanw/HTMLReader).
Then in the completion handler block, you can evaluate the response to check if it is an HTML page, and if it contains a login form.
I don't know this library, but I guess you can use it like this:
HTMLDocument *document = [HTMLDocument documentWithString:responseObjectFromServer];
HTMLNode *node = [document firstNodeMatchingSelector:#"form.login"];
if(node) {
// Perform second NSURLSession call with login data
}

Best way to chain multiple NSUrlConnection in iOS

I couldn't find a reasonable answer so i'll ask for this specific case.
I must perform a http call (setting a specific header as a token value) and parse the returned json. The json can both return a "operation completed" message or a "token expired" message.
In case of token expired i must execute another http call which will provide me the refreshed token, set the token as header and re-execute the original http call.
I decided to adopt this solution: from the main thread, i'm gonna create another thread using
...
dispatch_async(feedQueue
...
and in this thread i'm gonna performing the above descripted calls as synchronous calls using
...
[NSURLConnection sendSynchronousRequest:urlRequest
...
and only at the end of the flow i call the main thread passing results to a block.
This way code is simple, easy to read, it has a linear flow, it is completely scoped inside a unique thread, and i don't mess with delegates and different "finite-states" to manage calls chain.
But i'm not sure if this is the best approach for my specific use-case as i've read similar questions where people suggest to adopt asynchronous calls, using both finite-states or NSOperation instances, but it seems to me that both these approaches based on asynchronous calls, are not the best solutions for a http calls chain.
What is the best approach? How can i correctly implement a chain of http call?
The correct way to set this up would be to use asynchronous calls for NSURLConnection. In the connectionDidFinishLoading you will intercept the connection that just finished and launch the next chained http connection.
Setup a private variable called _connection1;
So basically, you will do something like this
NSURLRequest * request1 = ...
_connection1 = [[NSURLConnection alloc] initWithRequest:request1 delegate:self];
In the
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
if([connection isEqual:_connection1]){
NSURLRequest *request2 = ...
_connection2 = [NSURLConnection...]
}
if([connection isEqual:_connection2]){
NSURLRequest *request3 = ...
_connection2 = [NSURLConnection...request3 ]
// And so on ....
}
}

With iOS asynchronous http request, how do you connect the received data to the request?

I'm loading certain images from a certain server asynchronously. I'm firing a number of requests at once using NSURLConnection connectionWithRequest method and receive the data using NSURLConnectionDelegate didReceiveData.
At didReceiveData, how do I know which request this data matches? At didReceiveResponse I can use the URL method of the response given as a parameter, but in didReceiveData I only have the received data.
It seemed like the perfect answer would be to use NSURLConnection sendAsynchronousRequest, as the completion handler has all the required parameters: (NSURLResponse*, NSData*, NSError*). I can use [response URL] to make a match to the original request... except in one case: not all the images I try to download exist. In that case, the request is redirected to a generic error page and the URL of that generic page is received, so I can't match the response to a request I've made. This I could handle with connectionWithRequest, but I can't get the best of both worlds.
In
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
you can use
NSURLRequest *request = [connection originalRequest];
to get the request that the connection was started with.
(This method is available since iOS 5.0, but I could not find it in my Xcode iOS 5.1 Library. You find it in the iOS 6 Library, in the header file NSURLConnection.h or here: http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLConnection/originalRequest).
More methods to manage multiple connections can be found in this thread: Managing multiple asynchronous NSURLConnection connections.
If you use sendAsynchronousRequest:queue:completionHandler: then you can just use the request parameter in the completion block.
At connection:didReceiveData: the first parameter is the NSURLConnection instance. So I don't understand where the problem is. You create a connection, then you send a request to that connection and the delegate receive the connection:didReceiveData with the connection value.
If you are using the same delegate for all the request you have to check the connection so you can say which request is associated to.
Perhaps you have to maintain a table of connection/request pairs.

Google App Engine. Google app engine seeing POST requests as a GET

I'm having the strangest issue right now with google app engine. I'm sending a POST request from iOS and google app engine instead invokes the GET handler.
I've sandboxed this one situation for testing and can't get it figured out. I have an iOS app that just sends a request. And I've commented out everything on GAE except for the service. The service only logs a parameter and returns.
The iOS app I've tried using two different ways of sending the request. Neither works.
iOS Code:
/*
NSURL * url = [NSURL URLWithString:#"http://beermonster-gngrwzrd.appspot.com/TestParameter"];
ASIFormDataRequest * _fdrequest = [[ASIFormDataRequest alloc] initWithURL:url];
[_fdrequest setPostValue:#"hello" forKey:#"testkey"];
[_fdrequest startAsynchronous];
*/
NSURL * __url = [NSURL URLWithString:#"http://beermonster-gngrwzrd.appspot.com/TestParameter"];
NSMutableURLRequest * __request = [NSMutableURLRequest requestWithURL:__url];
[__request setHTTPMethod:#"POST"];
NSString * post = [NSString stringWithFormat:#"testkey=hello"];
[__request setHTTPBody:[post dataUsingEncoding:NSUTF8StringEncoding]];
[NSURLConnection sendSynchronousRequest:__request returningResponse:nil error:nil];
My App engine handler:
class TestParameter(webapp.RequestHandler):
def post(self):
logging.debug(self.request.get("testkey"))
self.response.out.write(self.request.get("testkey"))
print self.request.get("testkey")
def get(self):
logging.debug("get")
logging.debug(self.request.get("testkey"))
self.response.out.write(self.request.get("testkey"))
The output in the GAE logs shows the "get" code path which isn't correct.
Any ideas why POST requests would come into GAE as a GET? Is there some configuration in GAE that I missed?
Thanks!
Check the entry in app.yaml for the script that handles "/TestParameter". Does it specify "secure: always"? If it does and you make a non-secure connection you will get a 302 redirecting to the secure version.
To fix this either make your post over HTTPS or remove "secure: always" from the entry in app.yaml.
From what I can tell if you want to send POST requests to GAE. Make sure you do it on https. If you make the request on a non-https attempt, it sends back a 302 redirect to the https version of the request. But if whatever you're using to send the request doesn't correctly handle 302's it might resend the request incorrectly.

How to write data to the web server from iPhone application?

I am looking forward for posting some data and information on the web server through my iPhone application. I am not getting the way to post data to the web server from iPhone sdk.
It depends in what way you want to send data to the web server. If you want to just use the HTTP POST method, there are (at least) two options. You can use a synchronous or an asynchronous NSURLRequest. If you only want to post data and do not need to wait for a response from the server, I strongly recommend the asynchronous one, because it does not block the user interface. I.e. it runs "in the background" and the user can go on using (that is interacting with) your app. Asynchronous requests use delegation to tell the app that a request was sent, cancelled, completed, etc. You can also get the response via delegate methods if needed.
Here is an example for an asynchronous HTTP POST request:
// define your form fields here:
NSString *content = #"field1=42&field2=Hello";
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://www.example.com/form.php"]];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[content dataUsingEncoding:NSISOLatin1StringEncoding]];
// generates an autoreleased NSURLConnection
[NSURLConnection connectionWithRequest:request delegate:self];
Please refer to the NSURLConnection Class Reference for details on the delegate methods.
You can also send a synchronous request after generating the request:
[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
If you pass a NSURLResponse ** as returning response, you will find the server's response in the object that pointer points to. Keep in mind that the UI will block while the synchronous request is processed.

Resources