Gillius's Programming

JSONP Requests with RestyGWT Marshaling

I have been experimenting with GWT using a RESTful (jersey server) backend as its data source.  When deploying both my application and the backend to the same server this worked great, but once the server side was finished I wanted to deploy it to Tomcat on my Amazon EC2 instance, and continue debugging the application using GWT's Developer mode.  This, unfortunately, does not work, due to Same Origin Policy restrictions of virtually all browsers.

The most standard way of circumventing the Same Origin Policy is to use Json with Padding, or JSONP.  The technique in a nutshell is that the server returns a JavaScript function call.  The client thas prepared for this by creating this function in the DOM of the current page in the browser.  The client tells the server "The name of the function is xyzzy" by sending in a query parameter, usually named "callback." This post shows some ways around this issue.

RestyGWT seems to have support for this.  A normal RestyGWT service is created like this:

public interface MyService extends RestService {
    @GET
    @Path("/object")
    public void getSomeObject(MethodCallback<SomeObject> callback);
}

To convert this to use JSONP you just add an annotation;

public interface FlickrService extends RestService {

    @GET
    @Path("/object")
    @JSONP(callbackParam="calback")
    public void photoFeed(MethodCallback<SomeObject> callback);    
}

I have shown that you can name the callbackParam, though if you don't "callback" is the default.  You can also specify a parameter failureCallbackParam, which sets up a second client-side method to receive error objects (for example if you wanted to have your service return a detailed exception object).

Unfortuantely, none of this seems to actually work:

One alternative to all of this is to forget RestyGWT entirely and use GWT's JsonpRequestBuilder class directly.  This is relatively straight-forward, and behaves, from the server side, exactly how you'd expect: it calls the resource and adds a query parameter "callback" (or something different, if you specify).  The javadoc linked above has a good example of how to make this work.

This, too, has a down side, though: the objects received by your code will be some derivative of JavaScriptObject that you define as a GWT Overlay Type.  This is, in my opinion. rather messy boilerplate.  Jason tells me it gives you the worst of both worlds (Java and JavaScript) by forcing you to deal with both.  This complication is the main reason RestyGWT is so useful - it takes care of all the json to java object marshaling for you.

An Alternative

Fortunately, there's a way to take advantage of RestyGWT's marshalling capabilities and avoid the incomplete or buggy parts.  The trick is to use JsonpRequestBuilder to make the call to the backend, but use RestyGWT to parse the JSONP result.  This works great.  You begin by explicitly creating a codec for your object:

public interface MyCodec extends JsonEncoderDecode<MyObject> { }

// ...

MyCodec myCodec = GWT.create(MyCodec.class);

JsonpRequestBuilder jsonp = new JsonpRequestBuilder();
jsonp.setCallbackParam("callback"); //if you wish to change it
jsonp.setTimeout(15000);
jsonp.requestObject(url, new AsyncCallback<JavaScriptObject>() {

   public void onFailure(Throwable throwable) {
       GWT.log("Error: " + throwable);
       updateTimer.schedule(20000);
   }

   public void onSuccess(JavaScriptObject jso) {
       GWT.log("Wow, it worked!");

       JSONObject o = new JSONObject(jso);
       MyObject myObject = myCodec.decode(o);

       //Do something wth your MyObject here
   });
}

This works exactly the way you would expect, both on the server and the client side (all the server see is a GET, no more OPTIONS and no special headers required).

The Server Side

Jersey makes generation of JSONP responses fairly easy.  The best way to demonstrate is by example:

@GET
@Path("/object")
@Produces({"application/x-javascript")
public Response getLocationsByCustomerJsonP(@QueryParam("callback") String callback) throws Exception {

    /*
     * Note that no @DefaultParam is required.  If the paramter isn't specified, then it will
     * be null, and JSONWithPadding's constructor will default to "callback"
     */


    MyObject returnObject = getMyObject(); //get or create the object you want to return

    JSONWithPadding j = new JSONWithPadding(returnObject, callback);
    return Response.ok().entity(j).build();
}