Web services

The term web services means many different things depending on the source and the audience. To some it's a nebulous marketing term that is never pinned down; to others it's a very rigid and specific set of protocols and standards. We are going to tackle it as a general concept, without defining it to death, but not leaving it entirely undefined either.

Web services is a means of exposing an API over a technology-neutral network endpoint. It's a means to call a remote method or operation not tied to a specific platform or vendor and get a result. By this definition POX over the network POX is included, so is REST, and so is SOAP—and really so is any other method of exposing operations and data on the wire in a neutral manner.

POX, REST, and SOAP are by far the most common web services around, so they are where we will focus in this section. Each provides a general guideline for accessing data and exposing operations, each in a more rigorous manner than the previous, respectively. POX basically exposes chunks of XML over the wire, usually over HTTP. REST is a bit more detailed in that it uses the concept of resources to define data and then manipulates them with different HTTP methods using a URL-style approach (much like the Android Intent system in general, which we have explored in previous chapters). SOAP is the most formal of them all, imposing strict rules about types of data, transport mechanisms, and security.

All of these approaches have advantages and disadvantages, and these differences are amplified on a mobile platform like Android. Though we can't possibly cover all the details here, we will touch on the differences as we discuss each of these concepts. We will examine the use of a POX approach to return recent posts from the del.icio.us API, and we will then look at using REST with the Google Data AtomPub API. Up first is what is probably the most ubiquitous type of web service in use on the internet today, and therefore one you will come across again and again when connecting Android applications—POX.

6.5.1 POX—Putting it together with HTTP and XML

To work with POX we are going to make network calls to the popular del.icio.us online social bookmarking site. We will specify a username and password to log in to an HTTPS resource and return a list of recent posts, or bookmarks. This service returns raw XML data, and we will then parse it into a Jav-aBean-style class and display it as shown in figure 6.4.

NetworkExplorer

Filf in credentials and select GO to make deJJdo.us request for recent posts

NetworkExplorer

Filf in credentials and select GO to make deJJdo.us request for recent posts charlie.collins charlie.collins hnp;WcodtgOD|lt.<om/oiidroi<l/s»fnfhlcs/A()i Demos/ http://vedorrrMgic.com/ I

http T//ovi k i-ap« he o rg/WlCKFT/vvicket-gu icMfld-ibatis-eMmple.html hnpi//l lve.gnome.org/Dla hltp://erik.do«iienhurg.tom/200Sj'09/£ill-graph-visuali4a[ion.u'ithHiS pcctj-and-dol/

h n p: f / wiwm. m u i &ef ryiec ri. c om/o o i c wef/ http://nw»lshcom/d{KS/ivtorials/)Ql/i(jl/$lid«.hiinl hltp://w«w.pwtiodayxom/5hovi<hread.php?l=lS8340&highlight=hea d'gasktt http://vnm,iwlod9/corn/5hoWthrwd.php?t*TC295t&Hifhlifht"WQl

Figure 6.4 The del.icio.us recent posts screen from the NetworkExplorer application

Listing 6.10 shows the del.icio.us login and HTTPS POST Activity code from our NetworkExplorer application.

Listing 6.10 The del.icio.us HTTPS POX API with authentication from an Activity public class DeliciousRecentPosts extends Activity {

private static final String CLASSTAG =

DeliciousRecentPosts.class.getSimpleName(); private static final String URL_GET_POSTS_RECENT = B Include

"https://api.del.icio.us/v1/posts/recent?"; <-1 del.icio.us URL

. . . member var declarations for user, pass, output, and button (Views) omitted for brevity, Provide Handler private final Handler handler = new Handler () { <-1 to update U|

public void handleMessage(final Message msg) { progressDialog.dismiss();

String bundleResult = msg.getData().getString("RESPONSE"); output.setText(parseXMLResult(bundleResult));

@Override public void onCreate (final Bundle icicle) { super.onCreate(icicle);

this.setContentView(R.layout.delicious_posts); . . . inflate views omitted for brevity this.button.setOnClickListener(new OnClickListener() { public void onClick(final View v) { output.setText("");

performRequest(user.getText().toString(), pass ,getText () . toString()) ; <-1 Call local performRequest

} | with user and passttpClient

. . . onPause omitted for brevity private void performRequest(String user, String pass) { this.progressDialog = ProgressDialog.show(this,

"working . . .", "performing HTTP post to del.icio.us");

final ResponseHandler<String> responseHandler =

HTTPRequestHelper.getResponseHandlerInstance(this.handler);

HTTPRequestHelper helper =

new HTTPRequestHelper(responseHandler); helper .performPost (URL_GET_POSTS_RECENT, O Use helper user, pass, null, null); <1-' for HTTP

private String parseXMLResult (String xmlString) { <-1 String result

StringBuilder result = new StringBuilder();

SAXParserFactory spf = SAXParserFactory.newInstance();

SAXParser sp = spf.newSAXParser();

XMLReader xr = sp.getXMLReader();

DeliciousHandler handler = new DeliciousHandler() ;

xr.setContentHandler(handler);

xr.parse(new InputSource(new StringReader(xmlString))) ;

List<DeliciousPost> posts = handler.getPosts();

for (DeliciousPost p : posts) {

// log and or handle

return result.toString();

To utilize a POX service we need to know a little bit about it, beginning with the URL endpoint ©. To call the del.icio.us service we will again use a Handler to update the UI ©, and we will use the HttpRequestHelper we previously built and walked through in the last section. In this example we again have many fewer lines of code than if we did not use the helper (lines of code we would likely be repeating in different Activity classes). With the helper instantiated we call the performRequest method with a username and password ©. This method, via the helper, will log in to del.icio.us and return an XML chunk representing the most recently bookmarked items ©. To turn the raw XML into useful types we then also include a parseXMLResult method ©. Parsing XML is a subject in its own right, and therefore we will cover it in more detail in chapter 13, but the short takeaway with this method is that we walk the XML structure with a parser and return our own DeliciousPost data beans for each record. That's it—that's using POX to read data over HTTPS.

Building on the addition of XML to HTTP, above and beyond POX, is the REST architectural principle, which we will explore next.

6.5.2 REST

While we look at REST, we will also try to pull in another useful concept in terms of Android development: working with the various Google Data APIs (http:// code.google.com/apis/gdata/). We used the GDATA APIs for our RestaurantFinder review information in chapter 3, but there we didn't authenticate, and we didn't get into the details of networking or REST. Here we will uncover the details as we perform two distinct tasks: authenticate and retrieve a Google ClientLogin token and retrieve the Google Contacts data for a specified user. Keep in mind that as we work with the GDATA APIs in any capacity, we will be using a REST-style API.

The main concepts with REST are that you specify resources in a URI form and you use different protocol methods to perform different actions. The Atom Publishing Protocol (AtomPub) defines a REST-style protocol, and the GDATA APIs are an implementation of AtomPub (with some Google extensions). As noted, the entire Intent approach of the Android platform is a lot like REST. A URI such as content:// contacts/1 is in the REST style. It includes a path that identifies the type of data and a particular resource (contact number 1).

That URI does not say what to do with contact 1, however. In REST terms that's where the method of the protocol comes into the picture. For HTTP purposes REST utilizes various methods to perform different tasks: POST (create, update, or in special cases delete), GET (read), PUT (create, replace), and DELETE (delete). True HTTP REST implementations use all the HTTP method types and resources to construct APIs.

In the real world you will find very few true REST implementations. It is much more common to see a REST-style API. That means an API that doesn't typically use the HTTP DELETE method (many servers, proxies, and so on have trouble with DELETE) and overloads the more common GET and POST methods with different URLs for different tasks (by encoding a bit about what is to be done in the URL, or as a header or parameter, rather than relying strictly on the method). In fact, though many people refer to the GDATA APIs as REST, they are technically only REST-like, not true REST. That's not necessarily a bad thing; the idea is ease of use of the API rather than pattern purity. All in all, REST is a very popular architecture or style, because it's easy yet powerful.

Listing 6.11 is a quick example that focuses on the network aspects of authentication with GDATA to obtain a ClientLogin token and using that token with a subsequent REST-style request to obtain Contacts data by including an email address as a resource.

Listing 6.11 Using the Google Contacts AtomPub API with authentication public class GoogleClientLogin extends Activity {

private static final String URL_GET_GTOKEN =

"https://www.google.com/accounts/ClientLogin"; private static final String URL_GET_CONTACTS_PREFIX =

"http://www.google.com/m8/feeds/contacts/"; private static final String URL_GET_CONTACTS_SUFFIX = "/full"; private static final String GTOKEN_AUTH_HEADER_NAME = "Authorization"; private static final String GTOKEN_AUTH_HEADER_VALUE_PREFIX =

"GoogleLogin auth="; private static final String PARAM_ACCOUNT_TYPE = "accountType" ; private static final String PARAM_ACCOUNT_TYPE_VALUE =

"HOSTED_OR_GOOGLE"; private static final String PARAM_EMAIL = "Email"; private static final String PARAM_PASSWD = "Passwd"; private static final String PARAM_SERVICE = "service"; private static final String PARAM_SERVICE_VALUE = "cp"; private static final String PARAM_SOURCE = "source"; private static final String PARAM_SOURCE_VALUE = "manning-unlockingAndroid-1. 0";

private String tokenValue;

. . . View member declarations omitted for brevity private final Handler tokenHandler = new Handler() {

public void handleMessage(final Message msg) { progressDialog.dismiss();

String bundleResult = msg.getData().getString("RESPONSE"); String authToken = bundleResult;

authToken = authToken.substring(authToken.indexOf("Auth=") + 5, authToken.length()). trim();

tokenValue = authToken; <1-1 Set

GtokenText. setText (authToken) ; © tokenValue

Create Handler for contacts request private final Handler contactsHandler = new Handler() { public void handleMessage(final Message msg) { progressDialog.dismiss();

String bundleResult = msg.getData().getString("RESPONSE") output.setText(bundleResult);

. onCreate and onPause omitted for brevity

© Implement <_I getToken private void getToken(String email, String pass) { final ResponseHandler<String> responseHandler = HTTPRequestHelper.getResponseHandlerInstance( this.tokenHandler);

this.progressDialog = ProgressDialog.show(this,

"working . . .", "getting Google ClientLogin token");

HashMap<String, String> params = new HashMap<String, String>(); params .put (GoogleClientLogin. PARAM_ACCOUNT_TYPE,

GoogleClientLogin. PARAM_ACCOUNT_TYPE_VALUE) ; params.put(GoogleClientLogin.PARAM_EMAIL, email) params.put(GoogleClientLogin.PARAM_PASSWD, pass) params.put(GoogleClientLogin.PARAM_SERVICE,

GoogleClientLogin. PARAM_SERVICE_VALUE) ; params.put(GoogleClientLogin.PARAM_SOURCE, GoogleClientLogin. PARAM_SOURCE_VALUE) ;

HTTPRequestHelper helper =

new HTTPRequestHelper(responseHandler); helper.performPost(HTTPRequestHelper.MIME_FORM_ENCODED, GoogleClientLogin. URL_GET_GTOKEN, null, null, null, params); <-1 Perform POST

Include necessary parameters for

ClientLogin private void getContacts(String email, String token) { final ResponseHandler<String> responseHandler = HTTPRequestHelper.getResponseHandlerInstance( this.contactsHandler);

< | Implement © getContacts this.progressDialog = ProgressDialog.show(this,

Encode email address in URL

"working . . .", "getting Google Contacts") ;

HashMap<String, String> headers =

new HashMap<String, String>(); headers .put (GoogleClientLogin. GTOKEN_AUTH_HEADER_NAME, GoogleClientLogin. GTOKEN_AUTH_HEADER_VALUE_PREFIX

encEmail = URLEncoder.encode(encEmail, J

} catch (UnsupportedEncodingException e) { // log and or handle

String url =

GoogleClientLogin. URL_GET_CONTACTS_PREFIX + encEmail + GoogleClientLogin. URL_GET_CONTACTS_SUFFIX;

HTTPRequestHelper helper = new

HTTPRequestHelper (responseHandler) ; 1) Make GET request helper .performGet (url, null, null, headers); <-' for Contacts

After a host of constants that represent various String values we will use with the GDATA services, we have several Handler instances in this class, beginning with a tokenHandler O< This handler updates a UI TextView when it receives a message, like the previous similar examples we have seen, and updates a non-UI member tokenValue variable that other portions of our code will use ©. The next Handler we have is the contactsHandler that will be used to update the UI after the contacts request ©.

Beyond the handlers we have the getToken method ©. This method includes all the required parameters for obtaining a ClientLogin token from the GDATA servers (http://code.google.com/apis/gdata/auth.html) Q. After the setup to obtain the token, we make a POST request via the request helper ©.

Once the token details are taken care of, we have the getContacts method ©. This method uses the token obtained via the previous method as a header I. After you have the token you can cache it and use it with all subsequent requests (you don't need to re-obtain the token every time). Next we encode the email address portion of the Contacts API URL ©, and we make a GET request for the data—again using the HttpRequestHelper 1).

With this approach we are making several network calls (one as HTTPS to get the token and another as HTTP to get data) using our previously defined helper class. When the results are returned from the GDATA API, we parse the XML block and update the UI.

GDATA ClientLogin and CAPTCHA

While we have included a working ClientLogin example here, we have also skipped over an important part—CAPTCHA. Google may optionally require a CAPTCHA with the ClientLogin approach. To fully support ClientLogin you need to handle that response and display the CAPTCHA to the user, then resend a token request with the user's entered CAPTCHA value. For details see the GDATA documentation.

Now that we have explored some REST-style networking, the last thing we need to discuss with regard to HTTP and Android is SOAP. This topic comes up frequently in discussions of networking mobile devices, but sometimes the forest gets in the way of the trees in terms of framing the real question.

6.5.3 To SOAP or not to SOAP, that is the question

SOAP is a powerful protocol that has many uses. We would be remiss if we didn't at least mention that while it's possible, it's not generally recommended on a small, embedded device like a smartphone, regardless of the platform. The question within the limited resources environment Android inhabits is really more one of should it be done rather than can it be done.

Surely some experienced developers, who may have been using SOAP for years on other devices, are snarling at this sentiment right now. To those of you in that camp we would ask you to bear with us as we try to explain. The things that make SOAP great are its support for strong types (via XML Schema), its support for transactions, its security and encryption, its support for message orchestration and choreography, and all the related WS-* standards. These things are invaluable in many server-oriented computing environments, whether or not they involve the enterprise. And these things add a great deal of overhead, especially on a small, embedded device. In fact, in many situations where people use SOAP on embedded devices, they often don't bother with the advanced features—and they use plain XML with the overhead of an envelope at the end of the day anyway. On an embedded device you will often get better performance, and a simpler design, by using a REST- or POX-style architecture and avoiding the overhead of SOAP.

There are, of course, some situations where it makes sense to investigate using SOAP directly with Android. In the case where you need to talk to existing SOAP services that you have no control over, SOAP might make sense. Also, if you already have J2ME clients for existing SOAP services, you may be able to port those in a limited set of cases. Yet, either of these approaches makes it easier on only you, the developer, and has either no effect or a negative one in terms of performance on the user. Even when you are working with existing SOAP services, remember that you can often write a POX/REST-style proxy for SOAP services on the server side and call that from Android, rather than using SOAP directly from Android.

If you feel like SOAP is still the right choice, you can use one of several ports of the kSOAP toolkit (http://ksoap2.sourceforge.net/), which is specially designed exactly for SOAP on an embedded Java device. Keep in mind, though, even the kSOAP documentation states, "SOAP introduces some significant overhead for web services that may be problematic for mobile devices. If you have full control over the client and the server, a REST-based architecture may be more adequate." In addition, you may be able to write your own parser for simple SOAP services that don't use fancy SOAP features and just use a POX approach that includes the SOAP XML portions you require (you can always roll your own, even with SOAP).

All in all, in our minds the answer to the question is not to use SOAP on Android, even though you can. Our discussion of SOAP, even though we don't advocate it, rounds out our more general web services discussion, and that wraps up our networking coverage.

0 0

Post a comment