Friday, December 9, 2011

GWT RPC (Part 2 of 3) - The server-side RPC implementation

GWT is all about creating web applications. And a web application wouldn't be a web application without client/server communication. A mechanism called GWT RPC (remote procedure call) is used for this communication in GWT. In this series of tutorials, I'm going to show you how GWT RPC works by building a simple GWT application that uses the Twitter API to perform tweet searches and retrieve the Twitter privacy policy.

The process involves three steps. I'll be covering step 2 in this blog post.

  1. Create the synchronous and asynchronous interfaces that define each RPC method
  2. Create the server-side implementation of each RPC method
  3. Create the UI (user interface) that the client will use perform the searches

You can download the entire sample project used in this tutorial here.


Since we've already defined our RPC methods in the TwitterSearchService synchronous interface, we're now ready to write the implementations of those RPC methods. We'll create a new class named TwitterSearchServiceImpl (the naming convention is to use the synchronous interface's name with "Impl" appended to the end). It will be located in the "server" package because it's meant to only run on the server. It will extend the RemoteServiceServlet class and implement our TwitterSearchService synchronous interface (it should not implement the asynchronous interface).

package com.acme.twittersearch.server;

[imports...]

public class TwitterSearchServiceImpl extends RemoteServiceServlet implements TwitterSearchService {
  [...]
}

We do a little bit of error checking in the searchTweets method. If the search query is empty, then an IllegalArgumentException is thrown. With GWT RPC, exceptions are sent over the wire to the client. So the exact same exceptions can actually be handled in the client code (as you'll see in my next blog post). Exceptions can be used to return validation errors, as well as return unexpected server-side errors to the client.

@Override
public List<Tweet> searchTweets(String query) throws IllegalArgumentException, IOException {
  query = query.trim();
  if (query.isEmpty()) {
    throw new IllegalArgumentException("No search query specified.");
  }

  // see: https://dev.twitter.com/docs/api/1/get/search
  String q = URLEncoder.encode(query, "UTF-8");
  URL url = new URL("http://search.twitter.com/search.json?q=" + q);
  HttpURLConnection connection = (HttpURLConnection) url.openConnection();
  InputStream response = null;
  try {
    response = connection.getInputStream();
    return parseSearchResponse(response);
  } finally {
    if (response != null) {
      response.close();
    }
  }
}

Also in the searchTweets method, notice how the search query is passed into the URLEncoder.encode() method. This URLEncoder.encode() method encodes the search query so that it can be safely included in the query string of the Twitter API URL. You should always encode query string parameters in this way (just like you should always encode data before passing it into a SQL query string to avoid SQL injection). If a value happens to contain a character that has a special meaning in a URL (like an equals sign or ampersand), then you're likely to run into trouble.

We use the HttpURLConnection class to make the calls to the Twitter API. This class is not on the JRE Emulation list, but that's OK because we're only using it on the server. If we were to use it on the client, we'd get an error because GWT does not know how to convert this class to Javascript.

We also use the google-gson library to parse the JSON-encoded responses that are returned from the Twitter API. The way google-gson works is that you create a class that mirrors the JSON data you are unmarshalling. Each field in the class is named after a field in the JSON data. Google-gson looks at these field names to determine how to unmarshall the JSON message. So, we've created a SearchResponse class for our tweet search API call and a PrivacyPolicyResponse class for our privacy policy API call to do just that. However, for this demonstration, we don't need to unmarshal all the data in each response, so I've only included fields for the data that we need. You can see examples of the full JSON responses for the search and legal/privacy methods on the Twitter API documentation page.

private List<Tweet> parseSearchResponse(InputStream response) throws IOException {
  Reader reader = new InputStreamReader(response);
  SearchResponse searchResponse = new Gson().fromJson(reader, SearchResponse.class);
  return searchResponse.results;
}

private class SearchResponse {
  public List<Tweet> results;
}

Under the covers, TwitterSearchServiceImpl is a plain old Java servlet, which means that only one instance of this class is created to handle all requests. Therefore, be sure to keep thread-safety in mind.

web.xml

Our TwitterSearchServiceImpl class must also be added to the web application's deployment descriptor (web.xml).

<web-app ...>
  [...]

  <servlet>
    <servlet-name>twitterSearchServlet</servlet-name>
    <servlet-class>com.acme.twittersearch.server.TwitterSearchServiceImpl</servlet-class>
  </servlet>  

  <servlet-mapping>
    <servlet-name>twitterSearchServlet</servlet-name>
    <url-pattern>/twittersearch/search</url-pattern>
  </servlet-mapping></span>

  [...]
</web-app>

As you can see, the url-pattern for the servlet is /twittersearch/search. The first part, twittersearch, is the name of our GWT module, which is defined in the module definition file (TwitterSearch.gwt.xml). The second part, search, is the value of the @RemoteServiceRelativePath annotation in the TwitterSearchService interface that we created in the previous blog post.

Tweet class

Let's finish by creating the Tweet class. Because we're unmarshalling JSON data into this class using google-gson, the field names in this class must be named after the fields in the JSON message. Also, GWT requires that all objects that are sent over the wire implement Serializable, so let's do that too. The class must also be put in the "client" package, because it will be used in client code.

package com.acme.twittersearch.client;

import java.io.Serializable;

public class Tweet implements Serializable {
  private String id;
  private String from_user;
  private String created_at;
  private String text;

  [getter/setter methods...]
}

In the next and final blog post of my GWT RPC series, you'll learn how to create a basic UI and how to call these RPC methods from the client.

2 comments:

Adi said...

Its a very informative blog !
Thank you. I was searching for this kind of tutorials online but couldn't find ne helpful.
Also, it was nice that you also provided the complete source code.

Michael Angstadt said...

Thanks for the kind words, Adi.