Web Services are widely used as a way for software applications to communicate and exchange information over computer networks. They are used both by publicly accessible websites such as Amazon, Google and Yahoo as well by a large number of internal corporate applications. Early use of the World Wide Web was primarily user-driven. Early web applications focused exclusively on direct interaction with a live user. Over time, a number of protocols emerged that allowed computers to communicate over a network using remote procedure calls. Some of the remote procedure protocols that arose included RMI, XML-RPC, DCOM, ONC and, of course, SOAP. Though the basic idea was compelling, it took a few years for a clear leader to emerge. By mid-2003, a protocol called SOAP had been established as the most popular format for traditional web services, thanks primarily to the fact that it was relatively easy to use, completely language agnostic, and worked over HTTP, the very same protocol used by the World Wide Web.
At the same time as SOAP was becoming established as the standard protocol for traditional web services, another movement was gaining acceptance, based on the published dissertation of Roy Fielding, one of the principal authors of the HTTP specification. Fielding’s dissertation, published in 2000, introduced the term “REST”, which is a quasi-acronym that stands for REpresentational State Transfer.
REST is a philosophy, not a specification like SOAP, and RESTful web services are modeled on the same design principles and protocol as the Web itself. RESTful web services are stateless, resource based, and cacheable. As a result, they generally require less overhead and are easier to implement than traditional web services, and clients can often be written using the standard networking libraries available in most programming languages. The number of RESTful web services has grown considerably over the last couple of years. Amazon, Flickr, Facebook, Yahoo and 37Signals, to name just a few, all now offer RESTful web services.
In this article, we’ll learn how to create a client that will interact with a RESTful web application that the authors have created for your testing pleasure. The code that you create in this article will work in both your Cocoa and Cocoa Touch applications. Though you’ll be learning how to interact with our RESTful web app, the concepts you learn will easily carry over to other RESTful web services.
Let’s get started.
So that you can play along at home and test out the code samples in this article, we’ve created a simple web application, based on Ruby on Rails, that implements a RESTful web service. The first step is to download the web application named countries (.DMG, 600 KB). It is based on a simple, database of public domain data containing four fields: A country index, country name, gross domestic product, and two-character country abbreviation.
Our application will perform operations on this database, based on RESTful requests received from a client. Understanding how to implement those requests from your Cocoa and Cocoa Touch applications is our ultimate goal.
When you download the countries web application (.DMG, 600 KB), it can live anywhere on your hard drive. Once it is downloaded, double-click the file REST Article Test Server.zip to create a folder called countries. We won’t need to do anything else to that folder.
Next up, let’s make sure we’ve got Ruby and Rails properly installed.
We won’t get into the specifics of Ruby programming in this article. Our goal is purely to make sure you have what you need to be able to run the countries web service locally on your machine. It happens to be based on Ruby, but RESTful servers can be written in any number of ways.
If you are interested in learning more about Ruby and Rails using Leopard, see Developing Rails Applications on Mac OS X Leopard, the first of a three-part series. Part of the article focuses on updating your Ruby install. Here are the steps you’ll need to take to do that.
Launch Terminal and type in the following commands:
sudo gem update --system‚Ä® sudo gem install rails‚Ä® sudo gem update sqlite3-ruby
You will be prompted for your system password after typing the first command. Type it in. When the commands have finished running, Ruby and Rails will be all updated.
In Terminal, use cd to move to the countries folder. One simple way to do this is to type cd, then a space, then drag the countries folder from the Finder and drop it on the Terminal window. Terminal will paste the path of the dropped object in the command line. Hit return and then you can launch the web service by typing:
./script/server
Our RESTful service, countries, will launch and stay running. Leave the terminal window open while you are working through the code samples. If you need to do anything else at the command line, open a new window. When you are all done, you can hit control-c in the original window to shut down the web service. To relaunch, make sure you are in the countries folder and issue the ./script/server command again.
The RESTful web service application you just launched provides access to a small, searchable country database. The service supports a simple query interface that allows you to search for countries by name. The web service also allows you to update the existing data, to insert new countries, and to delete countries.
NOTE: The countries application is useful for showing how web services work, but it is not a full-fledged production-ready web service.
RESTful web services provide data using exactly the same protocol as the World Wide Web. In fact, most of the web sites that you visit on a daily basis are RESTful web services and Safari can accurately be called a RESTful web service client.
There are four basic commands that RESTful web services can implement, which, not by coincidence, are all part of the HTTP 1.1 specification. The commands are GET, POST, PUT, and DELETE. Web services are not required to implement all of the commands, and each command has a defined purpose in the protocol. Many of us are familiar with the first two commands, GET and POST, because they are widely used on the Web.
Get is the most widely used REST command. Any time you type a valid URL into Safari’s address bar, you are firing off an HTTP 1.1 GET request to a web server. GET requests are intended for retrieving data and and not for anything else. GET requests should never cause existing data to be changed in any way and should not include form data. Your entire request must be fully specified by the GET URL. In the sample web service we’ll be using in this article, we can search for countries by name using a URL like this:
http://localhost:3000/countries/canada
This request tells the web service that we would like information about any country whose name contains “canada” (the query is case insensitive). What gets returned from a GET request could be just about anything. Most web services will document the format of the data they return but, if there’s no documentation available, you can simply look at the Content-Type header in the web services’ response to know what kind of data you retrieved. Our sample application returns XML data, but RESTful web services can return text, HTML, an image or anything else that can be specified using MIME-types.
Go ahead and give this a try. With your countries web service app running, click on the above link, or type it into your web browser. If you are successful, you should see something like this:
37 Canada 1274000000000 ca
Our application searches the database and returns all entries that match the query string “canada”. Each entry contains a country index, country name, gross domestic product, and two-character country abbreviation.
Now try this link:
http://localhost:3000/countries/c
Here’s the results we got when we did this:
5 American Samoa 510100000 aq 35 Cambodia 25790000000 cb 36 Cameroon 40010000000 cm 37 Canada 1274000000000 ca 38 Cape Verde 3709000000 cv 39 Cayman Islands 1939000000 cj 40 Central African Republic 3101000000 ct 41 Chad 15950000000 cd 42 Chile 234400000000 ci 43 China 7043000000000 ch 44 Colombia 320400000000 co 45 Comoros 1262000000 cn 46 Cook Islands 183200000 cw 47 Costa Rica 55950000000 cs 48 Cote d'Ivoire 32860000000 iv 49 Croatia 69440000000 hr 50 Cuba 51110000000 cu 51 Cyprus 21410000000 cy 52 Czech Republic 249100000000 ez 55 Dominica 485000000 do 56 Dominican Republic 85400000000 dr 57 Ecuador 98280000000 ec 68 France 2067000000000 fr 69 French Polynesia 4580000000 fp 76 Greece 326400000000 gr 89 Iceland 11890000000 ic 98 Jamaica 13470000000 jm 113 Liechtenstein 1786000000 ls 116 Macau 12500000000 mc 117 Macedonia 17260000000 mk 118 Madagascar 19950000000 ma 128 Mexico 1353000000000 mx 130 Monaco 976300000 mn 134 Morocco 127000000000 mo 141 New Caledonia 3158000000 nc 143 Nicaragua 18170000000 nu 159 Puerto Rico 77410000000 rq 166 Saint Lucia 1179000000 st 168 Saint Vincent and the Grenadines 902000000 vc 171 Sao Tome and Principe 278000000 tp 175 Seychelles 1655000000 se 182 South Africa 467600000000 sf 202 Turks and Caicos Islands 216000000 tk
This is a list of all countries that contain the letter “c”.
The next two commands need to be discussed together, because there is some overlap between them. In terms of the HTTP protocol, both the PUT and POST commands can be used to create new records, and both can be used to provide updated data for an existing record. Because both types of requests can be used to create or update a record, there is no hard and fast rule about which type of request should be used when accessing a specific web service, and you may encounter web services that use either for any given purpose. Despite the overlap, there are some general guidelines about when to use POST and PUT that have developed in the REST community.
Most of us know POST requests as the action used when web forms get submitted to a web server. Although there are many existing web applications that use the POST command for all transactions, in RESTful web services, the POST command should be used for creating new objects by providing a new chunk of data that will be used to create a new record. In general, POST should not be used for updating existing records, although you will may encounter web services that do so.
PUT requests look and feel an awful lot like POST requests. They are constructed in essentially the same way as POST requests. And, in fact, a PUT request can cause the creation of a new record or can be used to replace an existing record with new data. The use of PUT to create new records is allowed by the HTTP protocol, but generally discouraged when building RESTful web services unless there is some specific reason why PUT won't work. The HTTP specification places some limitations on the PUT command that it doesn't place on the POST command, including the limitation that The server should not trigger additional processing based on a PUT request or make changes to records other than the one specified in the URL.
The HTTP specification allows either PUT or POST to be used to create new records or to update of existing ones, so if you're accessing an existing web service, your best bet is to look at the documentation for that web service to determine which command to use for any given call. When creating new RESTful web services, you should generally stick to using POST for creating new records and PUT for updating existing ones.
The last command that RESTful web services can implement is the DELETE command, which does exactly what it sounds like: it causes a particular item to be removed or deleted from the application.
One of the main tenets of the REST philosophy is that RESTful web services should be stateless. This means that such a web service should never track sessions or rely on cookies in order to do their job. The client program is generally responsible for keeping track of any objects and state data it needs, so it can pass it back into the web service in future requests. So, a client might, for example, query using a GET command to get the ID and data for a specific object, and then later use a POST command to update that object by specifying the ID that was retrieved earlier along with the data that needs to be changed . The web service doesn’t even have to know that the two commands came from the same client. In a multi-server environment, the commands can be issued to completely different servers and they will still work, meaning that RESTful web services are extremely scaleable.
Let’s build a simple RESTful client so that we can see how this all works in Cocoa.
Because RESTful web services are built on top of HTTP, and because Cocoa and Cocoa Touch share a robust set of classes for interacting with web servers using the HTTP protocol, creating a GET request to a RESTful web service is actually quite simple. Because GET requests are completely specified by a URL, you can actually create one by simply using the initWithContentsOfURL: methods available in several classes, including NSString, NSData, and NSImage. Here’s an example, using NSString to retrieve data from our sample web service, dumping the result to the console:
NSString *queryTerm = @"canada"; NSString *baseURLString = @"http://localhost:3000/countries/"; NSString *urlString = [[NSString alloc] initWithFormat:@"%@%@", baseURLString, queryTerm]; NSURL *url = [[NSURL alloc] initWithString:urlString]; NSString *result = [[NSString alloc] initWithContentsOfURL:url]; NSLog(result); [url release]; [urlString release]; [result release];
We constructed a URL by combining our search term (contained in the variable queryTerm) with the base search URL. If you were to run this code specifying a search term of “canada”, the returned data would look like this:
<?xml version="1.0" encoding="UTF-8"?> <CountryData> <country> <id>37</id> <country>Canada</country> <gdp>1274000000000</gdp> <iso_code>ca</iso_code> </country> </CountryData>
Remember, the format of this data is not dictated by the REST philosophy; the service could have returned tab-delimited text, an image file, or just about anything else, and it would still be a RESTful web service.
The previous source code has the advantage of being short and sweet, but it does have some shortcomings: There’s no way to know for sure if the call was successful, and there’s no way to know what type of data is now sitting in result. In many circumstances, that may be okay. Perhaps you know in advance the type of data the web service returns and don’t really care all that much if your query was successful.
You may, however, need to know for sure that everything went okay, and you may want to be able to determine the type of data that was returned. You can accomplish both of these goals by checking the response sent back by the server. In order to do that, though, our code needs to be just a little bit longer.
NSString *queryTerm = @"canada"; NSString *baseURLString = @"http://localhost:3000/countries/"; NSString *urlString = [[NSString alloc] initWithFormat:@"%@%@", baseURLString, queryTerm]; NSURL *url = [[NSURL alloc] initWithString:urlString]; NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url]; [req setHTTPMethod:@"GET"]; NSHTTPURLResponse* response = nil; NSError* error = [[NSError alloc] init]; NSData *responseData = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error]; NSString *result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; NSLog(@"Response Code: %d", [response statusCode]); NSLog(@"Content-Type: %@", [[response allHeaderFields] objectForKey:@"Content-Type"]); if ([response statusCode] >= 200 && [response statusCode] < 300) NSLog(@"Result: %@", result); [urlString release]; [url release]; [result release]; [req release];
Since this code uses request and response objects that we created, we now have the ability to check the response for the content type and the status code and can now make sure that everything went okay or decide what to do with the returned data based on its content type.
Response codes in HTTP are three-digit codes, and a successful request will generate a response code that begin with a 2. With GET requests, a successful response will almost always be indicated by a status code of 200.
In the Countries application, POST requests are used to create newg records. We can see how POST requests work by inserting a new country into the database. We can do this by passing a POST request to our web service, providing the new data in the body of the request. The new data needs to be passed in as HTML form parameters, which follow the format “fieldname=fieldvalue”. Multiple parameters can be passed in by separating each field name and value pair using an ampersand, like this: “field1=value1&field2=value2”.
NSString *urlString = @"http://localhost:3000/countries/"; NSURL *url = [[NSURL alloc] initWithString:urlString]; NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url]; [req setHTTPMethod:@"POST"]; NSData *putParams = [@"country=Newtonia&iso_code=nt&gdp=257000" dataUsingEncoding:NSUTF8StringEncoding]; [req setHTTPBody: putParams]; NSHTTPURLResponse* urlResponse = nil; NSError *error = [[NSError alloc] init]; NSData *responseData = [NSURLConnection sendSynchronousRequest:req returningResponse:&urlResponse error:&error]; NSString *result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; NSLog(@"Response Code: %d", [urlResponse statusCode]); if ([urlResponse statusCode] >= 200 && [urlResponse statusCode] < 300) NSLog(@"Response: %@", result); [urlString release]; [url release]; [req release]; [result release];
Let’s say that we need to update Canada’s GDP. In the Countries application, we use the PUT comand to update an existing record. We have to indicate which object we are going to update right in the URL, just as we did in our GET request. With PUT, and DELETE requests, you are almost always going to specify a unique id in your URL, which you will usually determine by sending a GET request first. To update the country we retrieved from the GET request, which had an id of 37 (Canada), we would use the URL:
http://localhost:3000/countries/37
This code updates record 37 and assigns it a new GDP value using a POST request:
NSString *baseURLString = @"http://localhost:3000/countries/"; NSString *countryID = @"37"; NSString *urlString = [[NSString alloc] initWithFormat:@"%@%@", baseURLString, countryID]; NSURL *url = [[NSURL alloc] initWithString:urlString]; NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url]; [req setHTTPMethod:@"PUT"]; [req setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; NSData *paramData = [@"gdp=600" dataUsingEncoding:NSUTF8StringEncoding]; [req setHTTPBody: paramData]; NSHTTPURLResponse* urlResponse = nil; NSError* error = [[NSError alloc] init]; NSData *responseData = [NSURLConnection sendSynchronousRequest:req returningResponse:&urlResponse error:&error]; NSString *result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; NSLog(@"Response Code: %d", [urlResponse statusCode]); if ([urlResponse statusCode] >= 200 && [urlResponse statusCode] < 300) NSLog(@"Result: %@", result); [urlString release]; [url release]; [result release]; [req release];
As we did with the GET request, we check the response code to make sure that the operation succeeded. If the operation was successful, the web service should return a status code that begins with a 2. With a POST request, a 200 response will usually indicate that the operation was a success and that more information about the operation is provided in the body of the response. Other common status codes associated with successful POST operations are 201, which is just a simple acknowledgment that everything went okay and 202, which means that the change has been accepted, but the processing necessary for that change may not have finished yet.
An unsuccessful POST command will generally result in a response status code that begins with a 4. If you try to update a resource that doesn’t exist, for example, you will receive a 410 response, and if two different clients try to update the same resource at the same time, you could receive a 409 response. Any response that does not begin with a 2 means that your operation did not work, so you should make sure that you check the status code and take appropriate action.
As with the POST request, we will get a response status code that begins with a 2 if our request was successful. Notice that we specified multiple form values in the body of the request this time. PUT requests can fail if you do not provide data for required fields, or if you specify values that do not pass validation. If this happens, you will get a response code that begins with a 4. You might get a response code such as 400 Bad Request, 409 Conflict, or 412 Precondition Failed.
In order to remove records from a RESTful web service, we create a DELETE request. DELETE requests are similar in structure to GET requests. The item to be deleted is specified by the URL and no form data needs to be supplied. We just indicate which object is to be deleted using the URL. Usually, this is done by specifying the unique identifier for the row to be deleted, just as we did with our PUT and POST requests. In our sample web service, to delete a country with an ID of 100, we would issue a DELETE request using the following URL:
http://localhost:3000/countries/222
Here’s a sample DELETE request:
NSString *baseUrlString = @"http://localhost:3000/countries/"; NSString *countryID = @"222"; NSString *urlString = [[NSString alloc] initWithFormat:@"%@%@", baseUrlString, countryID]; NSURL *url = [[NSURL alloc] initWithString:urlString]; NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url]; [req setHTTPMethod:@"DELETE"]; NSHTTPURLResponse* urlResponse = nil; NSError *error = [[NSError alloc] init]; NSData *responseData = [NSURLConnection sendSynchronousRequest:req returningResponse:&urlResponse error:&error]; NSString *result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; NSLog(@"Response Code: %d", [urlResponse statusCode]); if ([urlResponse statusCode] >= 200 && [urlResponse statusCode] < 300) NSLog(@"Response: %@", result); [url release]; [baseUrlString release]; [req release]; [result release];
Once again, we make sure to check our response code to verify that our operation completed successfully. In a real application, of course, you would take appropriate steps if the call did not succeed.
RESTful web services are becoming increasingly popular, and not without good reason. They are easy to implement and easy to consume. As you’ve seen here, you can leverage standard HTTP functionality, such as that provided by NSMutableURLRequest, to quickly create RESTful web service clients.
Updated: 2008-11-14