Integrating iPhone Application with Shibboleth - iphone

Has anyone integrated an iPhone application with a Shibboleth Identity Provider? Googling did not come up with anything so I am asking the gurus directly.
If it has not been previously dones, is it feasible to do so?

The answer to both is "Yes."
I'm a Java guy, so being asked two weeks ago to:
Learn Objective-C
Write an native iPhone App
Authenticated programmatically with Shibboleth
Download an display Shibboleth protected datafile
...Was a little daunting. Compound that with the absence of any forum posts to help out has prompted me to share my experience.
Here's an overview followed by some hopefully very helpful sample code. Please vote for my answer if this helps! It worth a few weeks of my time :)
For an application on the iPhone to download Shibbolized resources, the following needs to happen:
Use the URL API's in Cocoa to submit the HTTP request for the resource in question.
Implement a delegate class for the request to:
Respond to the SP re-direct to the IdP (automatic courtesy of Cocoa)
Respond to server certificate trust challenges
Respond to user credential challenges
Respond to errors (if needed)
Receive IdP's "binding template" for the authenticated user, an HTML form which re-directs the user back to the SP with two parameters
Programmatically HTTP POST the two parameters from the IdP back to the SP.
Cookies are automatically stored and forwards courtesy of Cocoa again
Implement a second URL Request delegate to receive the originally request data.
Here are some useful references from Apple and Shibboleth:
http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
https://spaces.internet2.edu/display/SHIB2/IdPSPLocalTestInstall
And hopefully I can include all the source for a quick demonstration.
ApplicationDelegate.h
----------
#import <UIKit/UIKit.h>
#import "ConsoleViewController.h"
/*
The application delegate will hold references to the application's UIWindow and a ConsoleViewController.
The console does all of the interesting Shibboleth activities.
*/
#interface ApplicationDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
ConsoleViewController *consoleViewController;
}
#end
ApplicationDelegate.m
----------
#import "ApplicationDelegate.h"
#import "ConsoleViewController.h"
/*
The implementation for the ApplicationDelegate initializes the console view controller and assembles everything.
The console does all of the interesting Shibboleth activities.
*/
#implementation ApplicationDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Initialize the console.
consoleViewController = [[ConsoleViewController alloc] init];
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[window setBackgroundColor:[UIColor lightGrayColor]];
[window addSubview:[consoleViewController view]];
[window makeKeyAndVisible];
}
- (void)dealloc {
[window release];
[ConsoleViewController release];
[super dealloc];
}
#end
ConsoleController.h
----------
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/*
The ConsoleViewController's interface declares references to the network data used in negotiating with Shibboleth
and a UITextView used to display the final result or errors.
*/
#interface ConsoleViewController : UIViewController {
NSMutableData *responseData;
NSString *responseString;
UITextView *console;
}
#end
ConsoleController.m
----------
#import "ApplicationDelegate.h"
#import "ConsoleViewController.h"
/*
This delegate is used when making the second HTTP request with Shibboleth. If you're just getting here, start
by reading the comments for ConsoleViewController below.
All we need to do now is receive the response from the SP and display it.
If all goes well, this should be the secured page originally requested.
*/
#interface AuthenticationRedirectDelegate : NSObject {
NSMutableData *authResponseData;
NSString *authResponseString;
UITextView *console;
}
#property (nonatomic retain) UITextView *console;
#end
/*
Refer to the comments for the interface above.
*/
#implementation AuthenticationRedirectDelegate
#synthesize console;
-(id)init {
authResponseData = [[NSMutableData alloc] retain];
return self;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[authResponseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[authResponseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[console setText:[error localizedDescription]];
}
/*
Once the data is received from Shibboleth's SP, display it.
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
authResponseString = [[NSString alloc] initWithData:authResponseData encoding:NSUTF8StringEncoding];
[console setText:authResponseString];
[connection release];
}
#end
/*
The implementation of the ConsoleViewController, and AuthenticationRedirectDelegate above, contain the real logic of
this Shibboleth exercise. The ConsoleViewController performs the following:
1. Prepare the initial HTTP request to a Shibboleth protected resource.
2. Act as the delegate whilst Cocoa's URL Loading API receives the HTTP Response.
NOTE: We instruct Cocoa in advance to take care of the SP redirecting to the IdP, accepting the server certificate,
and submitting the user credentials
3. Once the HTTP Response is finished loading, parse the <form action, RelayState and SAMLResponse from the IdP's
response
4. Call a utility method to prepare a second HTTP POST Request to the <form action/SP with the IdP's parameters
NOTE: We do not need to transfer over any of Shibboleth's cookies, since Cocoa is doing this automatically
5. Use a new instance of AuthenticationRedirectDelegate to receive the POST's response, which should be the secured
page originally requested.
6. Display the final content in the UITextView known as console.
*/
#implementation ConsoleViewController
/*
A handy utility method for extracting a substring marked by two provided token strings.
Used in parsing the HTML form returned by the IdP after the first HTTP Request.
*/
+(id)substringFromString:(NSString *)source BetweenOpenToken:(NSString *)openToken AndCloseToken:(NSString *)closeToken {
NSUInteger l = [source length];
NSUInteger openTokenLen = [openToken length];
NSUInteger openTokenLoc = ([source rangeOfString:openToken]).location;
NSUInteger valueLoc = openTokenLoc + openTokenLen;
NSRange searchRange = NSMakeRange(valueLoc, l - valueLoc);
NSUInteger closeTokenLoc = ([source rangeOfString:closeToken options:NSCaseInsensitiveSearch range:searchRange]).location;
searchRange = NSMakeRange(valueLoc, closeTokenLoc - valueLoc);
NSString *result = [source substringWithRange:searchRange];
return result;
}
/*
This function takes the three properties returned by the IdP after the first HTTP request and
HTTP POSTs them to the SP as specified by the IdP in the "url" parameter.
*/
-(void)authReturnTo:(NSURL *)url WithRelay:(NSString *)relayState AndSAML:(NSString *)samlResponse {
// Here we assemble the HTTP POST body as usual.
NSString *preBody = [[NSString alloc] initWithString:#"RelayState="];
preBody = [preBody stringByAppendingString:relayState];
preBody = [preBody stringByAppendingString:#"&"];
preBody = [preBody stringByAppendingString:#"SAMLResponse="];
preBody = [preBody stringByAppendingString:samlResponse];
/* The SAMLResponse parameter contains characters (+) that the SP expects to be URL encoded.
Here we simply manually URL encode those characters. You may wish to harden this with proper
URL encoding for production use.
*/
NSString *httpBody = [preBody stringByReplacingOccurrencesOfString:#"+" withString:#"%2B"];
NSData *httpBodyData = [httpBody dataUsingEncoding:NSUTF8StringEncoding];
NSString *httpContentLength = [NSString stringWithFormat:#"%d", [httpBodyData length]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:12.0];
[request setHTTPMethod:#"POST"];
[request setValue:httpContentLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:httpBodyData];
// Submit the HTTP POST using the second delegate class to receive the response
AuthenticationRedirectDelegate *delegate = [[AuthenticationRedirectDelegate alloc] init];
delegate.console=console;
[[NSURLConnection alloc] initWithRequest:request delegate:delegate];
}
/*
When this UIViewController finishes loading, automatically prepare and send a request to the Shibboleth SP Web Server
for a secured resource.
*/
- (void)viewDidLoad {
[super viewDidLoad];
console = [[UITextView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[[self view] addSubview:console];
responseData = [[NSMutableData data] retain];
// TODO: Enter your own URL for a Shibboleth secured resource.
NSURL *url = [NSURL URLWithString:#"<URL>"];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:12.0];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
/* Control flows to the delegate methods below */
}
/*
Refer to Apple's docs on the URL Loading System for details.
http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
/*
Refer to Apple's docs on the URL Loading System for details.
http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
/*
This implementation in the delegate let's Cocoa trust my SP Web Server's self-signed certificate.
TODO: You will want to harden this for production use.
Refer to Apple's docs on the URL Loading System for details.
http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
*/
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust] || [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic];
}
/*
This implementation for the delegate does two things:
1. Respond to challenges for my server's self-signed certificate
2. Respond to the IdP's challenge for the username and password.
TODO: Enter your own username and password here.
Refer to Apple's docs on the URL Loading System for details.
http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
*/
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
// TODO: Enter the correct username and password below.
/*
WARNING: Using an incorrect user name and password will result in your application being re-challenged
by the IdP. Cocoa will return to this function in a never-ending loop. This can result in the message
"NSPosixErrorDomain Too many open files". You'll need to perform additional coding to handle this.
*/
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic])
[challenge.sender useCredential:[NSURLCredential credentialWithUser:#"<USERNAME>" password:#"<PASSWORD>" persistence:NSURLCredentialPersistenceNone] forAuthenticationChallenge:challenge];
else
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
/*
You may wish to add more code here to log errors.
Refer to Apple's docs on the URL Loading System for details.
http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
*/
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[console setText:[error localizedDescription]];
}
/*
Once Cocoa has received a (hopefully) authenticated response from the IdP, we parse out the relevant pieces and prepare to
HTTP POST them back to the SP as specified by the IdP in the <form action attribute.
Refer to Apple's docs on the URL Loading System for details.
http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
if([responseString rangeOfString:#"SAMLResponse"].length < 1)
{
[console setText:[#"Unexpected response:\n]n" stringByAppendingString:responseString]];
return;
}
NSString *relayState = [ConsoleViewController substringFromString:responseString BetweenOpenToken:#"RelayState\" value=\"" AndCloseToken:#"\"/>"];
NSString *SAMLResponse = [ConsoleViewController substringFromString:responseString BetweenOpenToken:#"SAMLResponse\" value=\"" AndCloseToken:#"\"/>"];
NSString *formAction = [ConsoleViewController substringFromString:responseString BetweenOpenToken:#"<form action=\"" AndCloseToken:#"\""];
NSURL *formActionURL = [[NSURL alloc] initWithString:formAction];
[self authReturnTo:formActionURL WithRelay:relayState AndSAML:SAMLResponse];
}
#end

I managed to do just that, but it took me some time to understand every step of the process and to reproduce it perfectly. If I have time, I might write a detailed tutorial, because I didn't find any help for a lot of problems I got. The thing is, it also depends on the website you want to connect to, so yours maybe does not follow the same path as mine (its process is the same as the one described here).
To see every request fired by my browser (Chrome) to connect, I used the developer tools Network panel, with 'Preserve log' checked.
A few hints :
1°) You need to get "_idp_authn_lc_key..." cookie. There's a request that set it for you, find it.
2°) You need the login ticket (LT-...). You'll probably find it in the body of the page that asks you your credentials.
3°) You need a service ticket (ST-...). Again, you will find it in the page that the previous request returned.
4°) You need SAMLResponse. Again, you will find it in the page that the previous request returned.
5°) Finally, you can log in by sending back SAMLResponse to the service provider. You should take care of the encoding, here. I had a few '+' or '=' that I needed to change to '%2B' and '%3D'. You will be given a "_idp_session" cookie, that will allow you to reconnect without all this mess.
If someone tries to do the same, I'd be happy to help ! Just send me a message.

I successfully implemented using EC's solution as a starting point. The only other thing I'd add is that you really have to pay attention to keeping only one request going at a time. In our implementation the authentication process would get confused between multiple asynchronous requests running concurrently. Using NSOperation to throttle the queue seemed to work great for me.

Related

how call nsurlconnection delegate methods continuosly in iphone

hi in one of my application. i have to send a request to the server (json server) many times continously.my url will be like this as mention below
#"http://185.185.116.51/servername/serverjspfilesname.jsp?filterID=21&ticket=65675656565656567"
actually i have many filter id's (filter id you can find at top).in order to chnage the filterid continously i used for loop like this as mention below
for(int i=0;i<[appdelegate.listOfFiltersArray count];i++)
{
filtersDataModelObject=[[ListOfFiltersDataModel alloc]init];
filtersDataModelObject=[appdelegate.listOfFiltersArray objectAtIndex:i];
homescreenstring=[NSString stringWithFormat:#"http://%#/servername/serverjspfilesname.jsp?filterID=%#&ticket=%#",Ip,filtersDataModelObject.filterID,[storeData stringForKey:#"securityTicket"]];
NSLog(#"url is %#",homescreenstring);
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:homescreenstring]];
connection=[[NSURLConnection alloc]initWithRequest:request delegate:self];
if(connection)
{
homeScreenResponseData=[[NSMutableData alloc]init];
}
else
{
NSLog(#"connection failed");
}
}
actually after each condition is satisfied in for loop i have to connect with the server for getting the data from the server using nsurlconnection delegate methods. but here after complete execution of for loop only nsurlconnection delegate methods are executing with last filterid which is getting from the appdelegate.listOfFiltersArray array.
but i would like to call the server for each filterid.
if anyone know please let me know.thanks in advance.
Create one count variable int count in .h file.
int count = 0 //in .m file
Now use this method:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//one request finished
count ++;
//request another using count
}
The solution that Prince proposed is not general as you will face the problem of defining the
"filterid" for each request.
And what you are doing is a bad approach. Dont mix the web request with your business logic code.The web stuff should be handled by a separate file handling all the requests throughout the app. And that class will implement delegation.
For delegation you need to do the following.
In your Network class header (Networkclass.h) add the protocol
#protocol NetworkControllerDelegate <NSObject>
-(void) UrlResponseRecieved :(NSData *)responseData;
-(void) UrlResponseFailed:(NSError *)error;
#end
and in NetworkClass.m (implementation fie) do the following
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (receivedData != nil)
{
if([delegate respondsToSelector:#selector(UrlResponseRecieved:)])
[self.delegate UrlResponseRecieved:receivedData];
[receivedData release];
receivedData = nil;
}
[connection release];
}
if you still get stuck you may refer to the following
What's wrong on following URLConnection?
or you may read any asynchronous downloading tutorial first.

NSURLRequest with multiple parameters

I am trying to set up a NSURLRequest to download a simple index.html with its externa style.css sheet but I am not quite sure how to do this.. I have only ever just formatted the URL of the request to the file I want.. but this has to be slightly different and I cannot find a good example of what I am trying to do.
this is my code so far:
#pragma mark - NSURLConnection methods
- (void)htmlRequest
{
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.mywebsite.com/index.html"]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [NSMutableData data];
} else {
// Inform the user that the connection failed.
NSLog(#"Connection Fail");
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// It can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is an instance variable declared elsewhere.
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// inform the developer of error type
}
// This method uses methodName to determin which Initalizer method to send the response data to in EngineResponses.m
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// EngineResponses *engineResponses = [EngineResponses sharedManager];
// [engineResponses GetManufacturers:receivedData];
NSString *myString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
NSLog(#"%#", myString);
}
as you can see I am just calling index.html directly.. I would like to know how to format my request so i get the index.html as well as style.css
any help would be greatly appreciated.
I always create a new data structure,which has a -connection property and a -request property,like this
#interface connectionWrapper : NSObject
#property(retain) NSURLRequest *request
#property(retain) NSURLConnection *connection
by retaining this data structure in an mutable array, you can distinguish the connections in callback methods by iterate the array and compare each connectionWrapper instance's -connection property with the connection parameter the of the callback method, if they match(points to a same object), then you can retrieve the -request property of the connectionWrapper instance, then -url property of NSURLRequest instance.
as I'm not an native English speaker, I think code is a better tutor.
-(NSURLRequest*)getRequestByConnection:(NSURLConnection*)connection
{
for(connectionWrapper *w in theArrayContainingAllConnectionWrappers)
{
if(w == connection)
return w.request;
}
}
In callback method:
-(void)connection:(NSURLConnection*)connection didReceiveResponse(NSURLResponse*)response
{
NSURLRequest *request = [self getRequestByConnection:connection];
NSURL *url = [request url];
/*apply different approach to different url/*
}
PS:it's very sad that NSURLConnection don't have a -request property so that we can retrieve the request associated with the connection easily.
One way or another, you will have to make 2 requests. Even if you open a web page directly in a web browser, the browser will make a separate request for the CSS file referenced in the HTML it downloads. If your application needs both the HTML and the CSS file, then you want it to make 2 separate URL requests, first to get the HTML and then to get the CSS file.
Now, just because 2 requests need to be made, that doesn't mean you will always need to write the code that makes those 2 requests. It may be that libraries like the ones recommended by #Slee automatically take the results of a first request, parse them out, and make requests for any referenced CSS files. I have not worked with them so I am not sure what they support, or if any libraries will do this for you.
One thing you may want to consider is loading the HTML and CSS through a UIWebView rather than handling it all manually. UIWebView will attempt to load, parse, and render an HTML file into a UI component. In the process it will load referenced CSS and JavaScript files and apply them to its rendering. If you want to do anything special like intercept the calls it makes to load the CSS file(s), you can implement the UIWebViewDelegate protocol and set the delegate of the the UIWebView. Within that delegate you can implement the -webView:shouldStartLoadWithRequest:navigationType: method to be notified when the web view is loading the CSS file. You can use the call to that method to look at the request that is being issued for the CSS and do something else interesting with the request.
do you know the name of the .css file?
If so I would just make 2 requests otherwise you will have to write a parser to look for the link to the css and make a second request anyways.
I'd also suggest looking into a library to handle the downlading of stuff - lot's of great libraries that can do the heavy lifting for you with advanced features.
Here's 3 I have used:
http://blog.mugunthkumar.com/coding/ios-tutorial-advanced-networking-with-mknetworkkit/
https://github.com/tonymillion/TMHTTPRequest
https://github.com/pokeb/asi-http-request

How to write a dowloader class for updating download progress in iOs

Here is my actual issue, as some suggested I want to write a class for handling the multiple download progress in a UITableView. I have no idea how to write a class for this, can somebody help with some tips or ideas?
The group to look at is NSURLRequest and NSURLConnection. The former let's you specify the request (URL, http method, params, etc) and the latter runs it.
Since you want to update status as it goes (I answered a sketch of this in your other question, I think), you'll need to implement the NSURLConnectionDelegate protocol that hands over chunks of data as it arrives from the connection. If you know how much data to expect, you can use the amount received to calculate a downloadProgress float as I suggested earlier:
float downloadProgress = [responseData length] / bytesExpected;
Here's some nice looking example code in SO. You can extend for multiple connections like this...
MyLoader.m
#interface MyLoader ()
#property (strong, nonatomic) NSMutableDictionary *connections;
#end
#implementation MyLoader
#synthesize connections=_connections; // add a lazy initializer for this, not shown
// make it a singleton
+ (MyLoader *)sharedInstance {
#synchronized(self) {
if (!_sharedInstance) {
_sharedInstance = [[MyLoader alloc] init];
}
}
return _sharedInstance;
}
// you can add a friendlier one that builds the request given a URL, etc.
- (void)startConnectionWithRequest:(NSURLRequest *)request {
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSMutableData *responseData = [[NSMutableData alloc] init];
[self.connections setObject:responseData forKey:connection];
}
// now all the delegate methods can be of this form. just like the typical, except they begin with a lookup of the connection and it's associated state
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSMutableData *responseData = [self.connections objectForKey:connection];
[responseData appendData:data];
// to help you with the UI question you asked earlier, this is where
// you can announce that download progress is being made
NSNumber *bytesSoFar = [NSNumber numberWithInt:[responseData length]];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[connection URL], #"url", bytesSoFar, #"bytesSoFar", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyDownloaderDidRecieveData"
object:self userInfo:userInfo];
// the url should let you match this connection to the database object in
// your view controller. if not, you could pass that db object in when you
// start the connection, hang onto it (in the connections dictionary) and
// provide it in userInfo when you post progress
}
I wrote this library to do exactly that. You can checkout the implementation in the github repo.

How to return data gotten from a web service in objective- c (iPhone)?

This might be a dumb question. Sorry if it is.
But Im working on a project that consumes web services. I can connect to the web service and get the data I need fine.
I would like to have a method that returns this data obtained from the web service to the caller. The only problem is that the data is only obtained inside the ConnectionDidFinishLoading method, and I can't access this data from my method.
here is my code, that works fine:
- (NSData *) dataForMethod:(NSString *)webMethod withPostString:(NSString *)postString
{
NSURL *url = [NSURL URLWithString:[SigameWebServiceAddress stringByAppendingFormat:#"%#%#", #"/", webMethod]];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
NSString *msgLength = [NSString stringWithFormat:#"%d", [postString length]];
[req addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[req addValue:msgLength forHTTPHeaderField:#"Content-Length"];
[req setHTTPMethod:#"POST"];
[req setHTTPBody: [postString dataUsingEncoding:NSUTF8StringEncoding]];
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
if (conn)
{
webData = [NSMutableData data];
}
// I WOULD LIKE TO RETURN WEBDATA TO THE CALLER HERE, BUT WEBDATA IS EMPTY NOW, THE
//connectionDidFinishLoading ONLY GETS CALLED WITH THE DATA I WANT AFTER THE COMPILER
//IS DONE EXECUTING MY METHOD.
}
-(void) connection:(NSURLConnection *) connection didReceiveResponse:(NSURLResponse *) response
{
[webData setLength: 0];
}
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data
{
[webData appendData:data];
}
-(void) connection:(NSURLConnection *) connection didFailWithError:(NSError *) error
{
NSLog(#"FATAL ERROR");
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSLog(#"DONE. Received Bytes: %d", [webData length]);
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
//---shows the XML---
NSLog(#"%#", theXML); //NOW, THIS IS THE DATA I WANT. BUT HOW CAN I RETURN THIS TO
//THE CALLER. I MEAN, THE CALLER THAT CALLED MY METHOD
//+ (NSData *) dataForMethod: withPostString:
}
Any help here is appreciated!
Thanks
There are really two ways to go about this.
Create a delegate interface
Use Blocks
I would strongly advise against using the synchronous methods - unless you are/have created your own asynchronous framework around them (i.e. you are manually starting another thread and executing your synchronous request on that thread). In the long run you will realize you need the requests to be async, and you'll have to re-work everything such that they are.
To give a quick overview of the two options I gave:
1. Create a delegate interface
The idea here is to create a class which performs the request, and create a protocol the caller must implement. When the request is complete, you will invoke a specified method on the delegate with the data:
The protocol might look something like this:
#protocol RequestClassDelegate <NSObject>
- (void)requestCompleted:(ResponseClass *)data;
- (void)requestError:(NSError *)error;
#end
The class which makes the request might look something like this:
#interface RequestClass : NSObject
- (void)makeRequest:(id<RequestClassDelegate>)delegate;
#end
And the request class implementation might contain some of the following, in addition to your connection logic:
#implementation RequestClass
{
__weak id<RequestClassDelegate> _delegate;
}
// Connection Logic, etc.
- (void)makeRequest:(id<RequestClassDelegate>)delegate
{
_delegate = delegate;
// Initiate the request...
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
// Processing, etc.
// Here we'll call the delegate with the result:
[_delegate requestCompleted:theResult];
}
#end
2. Use Blocks
This solution is much the same as the first solution - but, a bit more elegant in my opinion. Here, we'll change the RequestClass to use blocks instead of a delegate:
typedef void (^requestCompletedBlock)(id);
typedef void (^requestErrorBlock)(NSError *);
#interface RequestClass : NSObject
#property (nonatomic, copy) requestCompletedBlock completed;
#property (nonatomic, copy) requestErrorBlock errored;
- (void)makeRequest:(requestCompletedBlock)completed error:(requestErrorBlock)error;
#end
And the implementation of that might look something like this:
#implementation RequestClass
#synthesize completed = _completed;
#synthesize errored = _errored;
// Connection Logic, etc.
- (void)makeRequest:(requestCompletedBlock)completed error:(requestErrorBlock)error
{
self.completed = completed;
self.errored = error;
// Initiate the request...
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
// Processing, etc.
// Here we'll call the delegate with the result:
self.completed(theResult);
}
#end
It sounds like you are trying to use return the data synchronously from your method, but you are using an asynchronous method (using an NSURLConnection and presumably calling its start method) to begin retrieving data. If you really want your method to return its result synchronously, read on. As #Steve says in another answer, however, you may also reconsider your interface design and instead implement it using an asynchronous approach and use his recommendations for either a delegate or block-based interface.
If you want to return the data synchronously from your method, use a synchronous request. So change this part of your code:
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
[conn start]; // I presume you have this somewhere
if (conn)
{
webData = [NSMutableData data];
}
with something more like this:
NSURLResponse *response = nil;
NSError *error = nil;
webdata = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
if (webdata) {
return webdata;
}
else {
// Handle error by looking at response and/or error values
return nil;
}
You will no longer need any of your delegate code if you use this approach. You will be limited in some ways though. For example, if your web service requires authentication via something other than URL parameters you can't use this approach.
Steve's answer is great and I can only suggest the way using blocks. Actually, as I am new into Objective-C I implemented the approach steve outlined. It works perfectly.
The Post for more details and my own point of view you can find here:
http://kerkermeister.net/how-to-build-an-cocos2d-ios-app-communicating-with-a-restful-api-the-sequence/
The Post contains all the tiny steps you need to follow to get Steve's solution approach with blocks working. That includes:
- an updateable view that will render information as soon as retrieved from Web API asynchronously
- a controller invoking the HTTP request to the Web API
- the actual HttpRequest class that uses iOS standard NSURLConnections
- a model class that uses blocks as callbacks to update its data
Your going to have to either implement a separate method in which you use the data once the data has been returned by the connectionDidFinishLoading method or make the request synchronously. The reason I believe the above does not work is because the request is happening on a separate thread, so the main thread continues, but does not actually have the data.
This is a good way to do that if synchronous is what you want:
Does all NSURLConnections connect asynchronously? iOs
In order to download data from webserivce - use NSURLSession -
A URL session task that returns downloaded data directly to the app in memory.
// 1. create NSURL link to your webservice
NSString *dataUrl = #"DATA_LINK_TO_WEBSERVICE";
NSURL *url = [NSURL URLWithString:dataUrl];
// 2. create a NSURLSessionDataTask
NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession]
dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Handle response here
}];
// 3.resume the task
[downloadTask resume];
Refernces:
apple documentation refrence:
https://developer.apple.com/documentation/foundation/nsurlsessiondatatask?language=objc
Raywanderlich great cookbook:
https://www.raywenderlich.com/2392-cookbook-using-nsurlsession
Your going to need to parse the XML that comes back. There are some good Objective C XML parsers out there. One in particular is made for ease of use....
http://nfarina.com/post/2843708636/a-lightweight-xml-parser-for-ios
It's a very light weight parser for extracting the values you want from XML. I've used many times with great success and little hassle. Here is how I query a web address and turn it into data.
NSString *query = [NSString stringWithFormat:#"http://WEB_ADDRESS_FOR_XML];
NSURL *URL = [NSURL URLWithString:query];
NSData *data = [NSData dataWithContentsOfURL:URL];
Or with NSURLConnection, in the did receive data:
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data
{
//USE THE DATA RETURNED HERE....
}
Then use the Parser from my link to get the contents:
SMXMLDocument *document = [SMXMLDocument documentWithData:data error:NULL];
NSLog("\nXML Returned:%#",document);

How to make NSURLConnection file download work?

I have a ViewController declared as:
#interface DownloadViewController : UIViewController
<UITableViewDataSource, UITableViewDelegate>
and I want to use NSURLConnection to download files. NSURLConnection simply "doesn't start", the delegate methods don't work (for example connection:didReceiveResponse is never called) . I noticed in some sample code that the class was subclassing NSObject instead of UIViewController.
How do I combine it? I want to use ViewController methods but then I can't use NSURLConnection.
It's not so easy to find a fully explained example how to download file with NSURLConnection. Everyone only concentrates on the easy methods like didReceiveResponse.
Using a UIViewController instead of an NSObject should not be your problem here !
I'm using a NSURLConnection in an UIViewController with no issue !
Here is a part of my code (not sure it will compile as it is) :
//
// MyViewController.h
//
#import <Foundation/Foundation.h>
#interface MyViewController : UIViewController {
#protected
NSMutableURLRequest* req;
NSMutableData* _responseData;
NSURLConnection* nzbConnection;
}
- (void)loadFileAtURL:(NSURL *)url;
#end
-
//
// MyViewController.m
//
#import "MyViewController.h"
#implementation MyViewController
- (void)loadView {
// create your view here
}
- (void) dealloc {
[_responseData release];
[super dealloc];
}
#pragma mark -
- (void)loadFileAtURL:(NSURL *)url {
// allocate data buffer
_responseData = [[NSMutableData alloc] init];
// create URLRequest
req = [[NSMutableURLRequest alloc] init];
[req setURL:_urlToHandle];
nzbConnection = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];
[req release];
req = nil;
}
#pragma mark -
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Append data in the reception buffer
if (connection == nzbConnection)
[_responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (connection == nzbConnection) {
[nzbConnection release];
nzbConnection = nil;
// Print received data
NSLog(#"%#",_responseData);
[_responseData release];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// Something went wrong ...
if (connection == nzbConnection) {
[nzbConnection release];
[_responseData release];
}
}
#end
If you plan to download large files, consider storing the received packets in a file instead of storing it in memory !
If you're having problems, you could consider using the well regarded ASIHTTPRequest library to manage your download. It takes care of everything for you.
For example, just 2 lines will do it.
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDownloadDestinationPath:fullPathOfWhereToStoreFile];
Use "NSURLConnection asynchronously" search for the term and you'll find source. Or just NSURLConnection.
For example:
NSURLConnection NSURLRequest proxy for asynchronous web service calls
Using NSURLConnection from apple with example code
Objective-C Programming Tutorial – Creating A Twitter Client Part 1