GWT RPC calls with Http GET method

GWT’s RPC [1] component comes handy when communicating with java backends – it’s like RMI restricted to asynchronous calls, however it escapes any attempts to cache server responses. This post discusses a moderately simple way of making RPC responses cacheable by Http proxies and browsers.
I’m not going into the details of the RPC lifecycle here – if you’re reading this post you’ll be aware of them anyway. In that case you also know that RPC uses the Http POST method for delivering request to the server, which makes it impossible for the browser (or reverse proxies [2]) to cache the response. Occasionally you would like such requests to be cacheable, either indefinitely, for a period of time or until the resource changes. In this case the Http protocol offers several ways [3] of telling a client about the expiration date or expiration condition of a resource. My favourite ones are:

  • The Expires header or a response which tells the browser that it doesn’t need to request the resource or even check back before the expiration date
  • The Etag header which associates an ID or checksum with a resource. Once a browser knows about the Etag associated with a resource, it can request that resource by passing the last known Etag along with the request. The server may then use that information to tell the browser that the resource has not changed.
 The small print here says that this will work in practice only with Http GET requests – since RPC is using POST as the transfer method, none of the server responses are cacheable by either the browser or a web proxy, which is normally ok and desired, as a service invocation is usually a dynamic and volatile affair.

Yet, occasionally you know that a service response might be valid over a longer period of time or that the response is costly to compute (many database queries) or costly to transmit (large payload). In that case you’d like your responses to be cached by the client (i.e. a list of cities or countries). Let’s take a simple example of a service interface that returns a rarely changing object:


interface GeoService{
    String[] getListOfPlaces(String countryCode);
    double[] getGeoCoordinatesForPlace(String placeCode);
}
If you’ve been with us for a while, you know that places such as towns or villages do change as in appearing, being merged into others or even being disbanded, but at a low rate. Thus our GeoService has a good caching potential.

The corresponding asynchronous interface is:
interface GeoServiceAsync{

  void getListOfPlaces(String countryCode, AsyncCallback callback);

  double[] getGeoCoordinatesForPlace(String placeCode, AsyncCallback callback);
 

}
In order to switch from POST to a GET transport you’ll need to change your application at two places: 
  1. The client needs to send RPC requests as an Http GET and the service parameters as URL query parameters
  2. The server needs to accept GET requests instead of POST requests

1. Changing a request to GET involves setting up the client proxy of the service with a custom request builder. Since your service will probably have both methods that you want to cache and methods you don’t want to cache, you should either maintain a separate instance of the service proxy for each occasion or implement a smart switch within the request builder.

public void getGeoCoordinates(final String placeCode, final AsyncCallback callback){


 ((ServiceDefTarget)geoService).setRpcRequestBuilder(new RpcRequestBuilder(){ 

 @Override

 protected RequestBuilder doCreate(String serviceEntryPoint) { 


   return new RequestBuilder(RequestBuilder.GET, "/myapp/geoservice?placecode="+placeCode); 


   } 

}); 

  geoService.read(placeCode, callback); 

} 

Note that the service interface obviously doesn’t change, so we need to pass the request arguments to the read() method and the request payload will be constructed anyway – only that it will not be used as the request builder will ignore it.

2. Preparing the server for GET requests

GWT’s RemoteServiceServlet will reject any attempts to contact it via GET, so you will need to bypass it and write your own servlet or controller (when talking about Spring):

public void doPost(HttpServletRequest request, HttpServletResponse response) throws Exception{

    String placeCode = request.getParameter("placecode");
    Method method = geoService.getClass().getMethod("read", new Class[]{String.class});
    double[] coordinates = geoService.read(placeCode);
    String sPayload = RPC.encodeResponseForSuccess(method, coordinates);
    byte[] payload;
    payload = sPayload.getBytes("utf-8");
    response.setHeader("Content-Disposition", "attachment");
    response.setContentType("application/json;charset=utf-8");
    response.setContentLength(payload.length);
    ServletOutputStream stream = response.getOutputStream();
    stream.write(payload);

}

At this point you might want to add further details like an Etag header, expiration dates (and handling for requests that carry them), gzip compression of the response payload etc.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.