ACCESSING THE CLOUD FROM COCOA TOUCH

来源:互联网 发布:海岛奇兵火箭炮手数据 编辑:程序博客网 时间:2024/06/05 20:55

Everything is moving toward the cloud and unless you’re buildingcalculators, unit converters, or miniature golf score keepers youriPhone app needs to know how to get data from it. In this blog postI intend to demonstrate how to set up a simple server applicationand how to retrieve data from it and post data to it using CocoaTouch. I have chosen to use PHP on the server side because of it’ssimplicity and ubiquity, and because I’ve know it, somewhat. Youshould, however, be able to implement something similar using yourserver side language of choice.

In many cases when you go to access remote data, you do so througha web service API. While services based on such technologies asSOAP or XML-RPC are standards that provide reasonable methods forretrieving and updating data, REST seems to be the methodologygaining the most ground lately. For our purpose in this post Iwon’t get into great detail of how to implement a REST base webservice as, again, REST is not a specific implementation but rathera methodology. (Read up on it elsewhere if you don’t understandwhat this means). However, I will talk about it briefly so that youcan get on the right path for doing your own RESTimplementation.

What Is The Cloud

It seems that the term ‘cloud’ in this context has been around fora pretty long time, however, you can just think of it as, well, theInternet. If you access your data “in the cloud”, you are accessingyour data that is hosted on some server somewhere in the world. Theidea behind it being that you can always access it no matter whereyou are. If your data is not “in the cloud”, then it is hostedlocally only and only accessible from that location.

Amazon.com among other technology leaders has helped to make thismetaphor easier to understand. If you are familiar with Amazon S3(Simple Storage Service), then you know what it means to store yourdata “in the cloud”. Amazon has made it very cheap and very easy toaccess data that you store on their servers from anywhere in theworld. They provide a RESTful web service through which you cansecurely add, remove, and update data that you have stored there.If you have image or video assets, for example, that you find getaccessed a lot through your website, you may find that hostingthose files on S3 is cheaper than paying your own web host toprovide the bandwidth and storage for them. This is a perfectexample of what cloud computing is and provides.

On a more generic level, cloud computing can mean setting up yourown service and providing access to resources or data in the samefashion. The difference in this case, however, is that you aremanaging it all on your own. Let’s take a look at how you might dothis.

Sending Arbitrary Data

The simplest PHP script can really teach you a great deal aboutwhat is going on between client and server. Remember that since weare depending on the Apache web server to serve up our PHPresponses a huge portion of the work is already done. We don’t haveto worry about low-level networking APIs and sockets. Instead wecan create a simple connection using a URL and the NSURLConnectionclass and we’re most of the way there. Consider the following PHPcode sample.

123
<?phpprint $HTTP_RAW_POST_DATA;?>

With one line of code (not including the tags), we have justimplemented an echo server. Whatever you send to this script on theserver will be sent right back to you. You should understand thoughthat it is the body ofthe request that gets stored in this $HTTP_RAW_POST_DATA variable.So what does that mean?

This really means that you can send anything you want as the bodyof the request and this script will store it in the$HTTP_RAW_POST_DATA and then print itback as the response. You just specify in your request the type ofdata you’re sending. Say you want to send raw XML, for example, youspecify”‘text/xml” as the content-type of the request that you willhand off to your NSURLConnection as in the following codesnippet.

1234567891011121314151617181920
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]         initWithURL:[NSURL         URLWithString:@"http://www.cimgf.com/testpost.php"]][request setHTTPMethod:@"POST"];[request setValue:@"text/xml"               forHTTPHeaderField:@"Content-type"]NSString *xmlString = @"<data><item>Item 1</item><item>Item 2</item></data>"[request setValue:[NSString stringWithFormat:@"%d",        [xmlString length]]         forHTTPHeaderField:@"Content-length"][request setHTTPBody:[xmlString         dataUsingEncoding:NSUTF8StringEncoding]][[NSURLConnection alloc]         initWithRequest:request                    delegate:self];

When this request finishes, the same XML inthe xmlString variablewill be sent right back to our application and will be available inour delegate method, -(void)connectionDidFinishLoading:(NSURLConnection*)connection; assuming we’vebeen appending the data to anNSMutableData object in our delegatemethod, -(void)connection:(NSURLConnection *)connectiondidReceiveData:(NSData *)d;.

This example is just echoing back whatever we send, but if wewanted to get a little fancier, we could load and parse the XMLwith the PHP XML parser and respond back to the client withsomething more useful.

What is also interesting about this code is that you can replacethe body of the request with any data type you’re interested inPOSTing to your server. If you post image data, for example, youcan save that image data on the server side using something likethis PHP script:

123456789
<?php$handle = fopen("image.png", "wb"); // write binary fwrite($handle, $HTTP_RAW_POST_DATA); fclose($handle); print "Received image file.";?>

Keep in mind that this is very primitive and it does no sanitychecking on the body data. You would need to add that in order toimplement any real world server side application. That being said,these few lines of code demonstrate how you can send most any dataas the request body and process it on the server side. OurObjective-C code from earlier modified to support sending a PNGimage would look like this:

123456789101112131415
NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed@"localimage.png"])NSMutableURLRequest *request = [[NSMutableURLRequest alloc]         initWithURL:[NSURL         URLWithString:@"http://www.cimgf.com/testpostimage.php"]]; // Not a real URL.  [request setHTTPMethod:@"POST"];[request setValue:@"image/png"         forHTTPHeaderField:@"Content-type"];[request setValue:[NSString stringWithFormat:@"%d",         [imageData length]]         forHTTPHeaderField:@"Content-length"];[request setHTTPBody:imageData][[NSURLConnection alloc] initWithRequest:request delegate:self];

The request we are sending will be asynchronous so our UI will nothang, however, there is no accurate progress monitoring capability,so you would need to implement that if you want to have an idea ofhow long it is going to take to post the image up to the webservice. See the section called Simplifying CloudAccess below to see one solution toproviding progress.

Working With Web Forms

A lot of web applications began life as web forms in which the usercan obtain a list of records or a detail record based on input theuser provides. You can post form data programmatically using afairly trivial implementation not much different from the code wedemonstrated above. In the previous section we discussed that youcan send any arbitrary data to a web service or script by placingthat data in the body of the request. This is also true for sendingform data.

If you send form data, which is the default when you create anNSURLConnection object and use it to talk to your server, you willsee a string of key value pairs in the same format you wouldnormally see in a get request–somethinglike key1=value1&key2=value2&key3=value3,etc. This is what gets sent in the body for web form requests.

Consider the following Bug Reporter web form.

Yes, yes, I know–it’s beautiful. Looks like something you would seecirca 1995. Stick with me here as I’m trying to keep things simple.The form takes two fields and just formats the input and respondsto the user with what the user entered. This same form can also besubmitted using code similar to what we’ve already shown. Here ishow you would programmatically post data to this form using andNSURLConnection/NSURLRequest.

1234567891011121314151617
NSMutableURLRequest *request =         [[NSMutableURLRequest alloc] initWithURL:            [NSURL URLWithString:@"http://www.cimgf.com/test/testform.php"]][request setHTTPMethod:@"POST"]NSString *postString = @"go=1&name=Bad Bad Bug&description=This bug is really really super bad."[request setValue:[NSString         stringWithFormat:@"%d", [postString length]]         forHTTPHeaderField:@"Content-length"][request setHTTPBody:[postString         dataUsingEncoding:NSUTF8StringEncoding]][[NSURLConnection alloc]         initWithRequest:request delegate:self];

Notice that we are passing a variablecalled go inthe postString variable. This tells our PHP script to see therequest as if the submit button was clicked. You’ll notice in thePHP script that we are checking whether the submit button wasclicked with the call to isset($_POST['go']). Take a look at thecomplete PHP web form script.

12345678910111213141516171819202122232425
<html><head><title>Bug Reporter</title></head><body><h2>Fancy Bug Reporter</h2><hr /><?phpif (isset($_POST['go'])){        // Form was posted        print "User submitted bug: ". $_POST['name'] . ": " . $_POST['description'];}else{        // Form was not posted, display form?><form method="POST" action="<?php echo $PHP_SELF; ?>">Bug Name:<br /><input type="text" name="name" maxlength="100" /><br />Bug Description:<br /><input type="text" name="description" maxlength="1000" size="80" /><br /><input type="submit" name="go" value="Add Bug"></form><?php}?></body></html>

When the request finishes posting to this form, you will have thesame HTML code in your data object as what you would see if youwere to view the source in the actual web page after posting theform.

Simplifying Cloud Access

Ben Copsey developeda networking library called ASIHTTPRequest thatis a really good replacement for NSURLConnection and relatedclasses. There are a couple reasons I prefer it to NSURLConnection.These include:

  • You can specify a delegate selector when you create your requestobject that will get called back when the request fails orsucceeds. This is really just a preference overNSURLConnection/NSURLRequest as it just seems simpler to me.
  • If you do want to see accurate progress for either downloads oruploads, you can pass in a reference to a UIActivityIndicatorViewthat will be updated automatically
  • Different types of classes are available for different types ofrequests. For example, to post form data to a web form, you useASIFormDataRequest instead of the more generic ASIHTTPRequest. Ithandles setting up the request body correctly and even enables youto post a file to the form if the form accepts one.
  • While Ben still considers it experimental code,there is also support for access to Amazon’s S3. You createanASIS3Request objectand provide your S3 credentials to the request. Then you cancreate, update, or delete any assets you may be storing in your S3buckets.

Using the example I mentioned earlier of sending XML to our PHPecho script, the request would now look like the following usingthe ASIHTTPRequest object:

1234567
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];NSString *xmlString = @"<data><item>Item 1</item><item>Item 2</item></data>";[request appendPostData:[xmlString dataUsingEncoding:NSUTF8StringEncoding]];[request setDelegate:self];[request setDidFinishSelector:@selector(requestFinished:)];[request setDidFailSelector:@selector(requestFailed:)];[request startAsynchronous];

Then you implement your delegate selector as in the following:

1234567891011
- (void) requestFinished:(ASIHTTPRequest *)request{    NSString *response = [request responseString];    // response contains the data returned from the server.} - (void) requestFailed:(ASIHTTPRequest *)request{    NSError *error = [request error];    // Do something with the error.}

Similarly, if you want to submit data to a form like we did earlierusing the NSURLConnection/NSURLRequest combination, we can insteaduse the ASIFormDataRequest class.

1234567
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];[request setPostValue:@"1" forKey:@"go"];[request setPostValue:@"Bad Bad Bug" forKey:@"name"];[request setPostValue:@"This bug is really really super bad." forKey:@"description"];[request setDidFinishSelector:@selector(requestFinished:)];[request setDidFailSelector:@selector(requestFailed:)];[request startAsynchronous];

When this request completes, we will have the formatted HTML in theresponseString of the ASIHTTPRequest object:

12345
- (void) requestFinished:(ASIHTTPRequest *)request{    NSString *response = [request responseString];    // response contains the HTML response from the form.}

I have come to prefer using ASIHTTPRequest for network access,however, this is a matter of taste. It is a very clean and wellwritten library of classes, so I highly recommend it, but yourmileage may vary.

So What About REST?

As I said at the beginning, I’m not going to go into a lot ofdetail about how to set up a REST web service as implementation ofthe REST philosophy isreally up to the developer. However, here are a few points abouthow I go about it:

  • Createindividual server side scripts for each of the functions you wantto implement. Instead of creating onemaster script, create one for each function you want to implement.This helps you maintain the code as when you need to update or fixsomething, it can be very targted.

  • Make heavyuse of mod_rewrite in your Apacheserver. Just use Apache. If you’re usingsomething else, I’m sorry but you’re on your own. The rewritemechanism that mod_rewrite provides enables you to take difficultto read URLs and make them easy to understand. WordPress, which weuse for this blog, changes a URL that looks like thishttp://www.cimgf.com/?p=235, into something that looks like this:http://www.cimgf.com/2010/01/28/fun-with-uibuttons-and-core-animation-layers/using mod_rewrite . 

    It also so happens to provide a great way to implement a REST webservice. Say you want to look up a bug by it’s unique ID in a bugtracker database. Your request would normally look like thishttp://www.cimgf.com/showbug.php?id=1234. The mod_rewrite moduleallows you to define rewrite rules that would change this URL tohttp://www.cimgf.com/bugs/1234. The rule to do this is very simple.
    12
    RewriteEngine OnRewriteRule ^bugs/([[:alnum:]]+)$ /showbug.php?id=$1
    The RewriteRule line uses a regular expression to define how totranslate from the uglier URL to the pretty one. This regex meansthe following: “look for the word bugs atthe beginning of the parameters string, followed by a forwardslash, followed by one or more numbers until you reach the end ofthe line.” Notice the $1 in the second section. If you are notfamiliar with perl compatible regular expressions, this simplymeans use whatever was found between the parens in the regex. Inour case the parens are “capturing” the ID of the bug the user isrequesting. The rewrite rule is then passing that along to thereal showbug.php script. 

    You just add this to the .htaccess filein the directory where you are hosting your server side scripts andyou will be able to load the load your data using the REST lookingURL–assuming of course your Apache web server has mod_rewriteenabled. Even if you are using shared hosting, there is no excusefor a web host not to have this enabled. If it is not enabled, youneed to find a new web host.

  • Drive yourweb service with a scripted backend that connects to a commoditydatabase like MySQL.Sanity check all of the input you getfrom your user by looking for vulnerabilities such as SQL injectionattacks, but then insert the data into the database using standardSQL calls. If you receive the data, as XML for example, you cansimply parse the XML into a DOM object and insert it from there.Better yet, use a (PHP) server side module that converts from XMLto SQL for you.

  • Use aframework like Rails. Most of the folks Iknow who have used it have nothing to say but good aboutimplementing web services using Ruby on Rails. I can’t recommend itpersonally, but people I respect swear by it. It handles a lot ofthe heavy lifting of developing a web service by constructing theunderlying functionality for you after you have specified basicmeta data that defines the entities you want to manage.

    The down side of this option for me is that I need to learn yetanother programming language, Ruby. While I’m getting around to it,it hasn’t been on the top of my priority list.

  • Use S3 forAssets. This is simply a suggestion,however, I think it’s a good one. If all you need is to accessassets from your iPhone app, keep a structured data documentaround, like an XML document in your S3 bucket. Let it map outwhere the assets are stored in your S3 buckets. Then all you haveto do is maintain that one XML document and upload a new one eachtime your content needs to change. It will define for your appwhere the various assets are located along with any otherinformation you might want to provide about that asset.

Conclusion

Networking code has gotten much simpler to implement in recentyears, so accessing your resources in the cloud is a great solutionto many application development problems. There are some greattools out there these days regardless of whether you are developingthe client or the server. If you need to access the cloud, use thevarious libraries available to you and keep your designs simple.Doing so will yield great results.

If you have some suggestions for how to implement REST web servicesfor iPhone apps, leave them in the comments section below. Rememberthat all comments are moderated, so if you don’t see your commentappear right away, just be patient. We’ll approve it as soon aspossible. Until next time.

0 0
原创粉丝点击