GWT applications as REST clients: handling JSON responsibly
The issue
When dealing with GWT web apps, communication with the server is often done using GWT’s RPC implementation, which wraps nicely over the Java Servlet API, and encapsulates all the nitty gritty stuff such as object serialization and the like.
What they don’t tell you is that RPC has quite an overhead, which can build up as you recklessly add more and more services to your web-app. This overhead is acceptable, even desirable (no fuss of serializations), for handling Java APIs in the web-app’s server. It is, however, quite redundant when communicating with RESTful APIs, where another server will respond with JSON objects (data is already serialized).
The danger
Is when we start using GWT-RPC services as a boilerplate for any kind of communication, regardless of the remote server response type. There’s no sense in parsing JSON responses coming from a remote REST service back to POJOs in the web-app’s server — it will be parsed again to JavaScript as all code in the client package is essentially translatable java code.
The solution
Have the web-app’s server returning a JSON text response, request it via XHR on the client, and than use the response in its raw form — as JSON is a subset of JavaScript’s literal object notation, it can be used transparently within JavaScript code.
To implement this approach in GWT, create a servlet that returns the JSON as text (outputs it to the response’s writer). This servlet can than be called from the client using RequestBuilder (which wraps XHR), and overlay types can be used to convert each JSON response to Java managed objects on the client package, according to its contract.
Let’s assume the response for a certain call is:
{
"foo": "bar",
"fooList": [
"firstItem",
"secondItem",
"thirdItem"
]
}
Create a servlet serving JSON:
Don’t forget to configure the web-app’s deployment descriptor (
web.xml) accordingly.public class JsonDataServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String responseText; // insert code to communicate with a remote REST service (perhaps using URLConnection), and fill responseText with data. // obtain a writer to send the result to the client PrintWriter out = res.getWriter(); out.println(responseText); out.flush(); } }Construct an overlay type according to the response’s interface:
public class FooData { protected FooData() {} public native final String foo() /*-{ return this.foo; }-*/; public native final JsArray fooList() /*-{ return this.fooList; }-*/; }Create client side utilities to convert the JSON response to a concrete representation of a
JavaScriptObject:public static FooData asFooData(String json) { JSONValue jsonVal = JSONParser.parseStrict(json); JSONObject jsonObj = jsonVal.isObject(); FooData fooData = (FooData) jsonObj.getJavaScriptObject(); }Notes:
- Many resources suggest the use of
eval()inside a JSNI method, but I prefer to utilizeJSONParser.parseStrict()as it will first try and call JavaScript’sJSON.parse()on supporting browsers. - If you’re gonna use the
eval()implementation anyway, thejsonargument needs to be wrapped in parentheses before evaluation, i.e.eval('(' + json + ')').
- Many resources suggest the use of
Call the servlet:
RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, GWT.getModuleBaseURL() + "path/to/servlet"); rb.setCallback(new RequestCallback() { public void onError(Request request, Throwable exception) { // some error handling code here } public void onResponseReceived(Request request, Response response) { if (200 == response.getStatusCode()) { FooData fooData = asFooData(response.getText()); // some code to further handle the response here } } }); rb.send();
And voila! JSON all the way, no muss, no fuss.
Now you
What’s your take on this approach? have you done anything similar / have any suggestions / improvements? have you written a mind-blowing library that puts all this to shame?
Please share your thoughts.
Coming soon
How to create a custom JSON callback for RequestBuilder?