Archive

Archive for October, 2008

GWT and Cross Site JSONP in J2EE Container

October 10th, 2008 admin 1 comment

Before starting this tutorial entry its worth mentioning a few people who have helped my along the one. Firstly a member of the GWT Group named AdamT who helped clear up a lot of things when implementing Cross Site JSONP calls. We had a discussion on the GWT group:

Group Link

Secondly Dan Morill of Google who has wrote a very descriptive article on how to achieve this and much of this tutorial is based around his article.

Dans Article

And finally my boss Chris who after I implemented the code finally caused the light bulb that had been dwindling (due to high energy costs and the credit crunch )to flash on!

Ok on with the tutorial….

This tutorial aims to describe how to implement Cross Site Scripting using JSONP and GWT. This problem fell into my hands because I needed to make a GWT widget that could be hosted on anybodies web site that could communicate with our web server.

You can solve this in one of two ways – firstly host the widget on your server and then for the pages simply iFrame the page to load the content in on different websites. Thus getting over the XSS problem. This wasn’t an option for me, the ‘client’ in this case didn’t particularly want an iFrame and I cant say I blame them.

So the second way you do this is JSONP and some clever GWT native Javascript methods. I’ll take it as given that people understand JSON but if they don’t there are quite a few useful links from Dans article. Also I’ll assume that people are used to tuning their web.xml file so that URL’s are mapped to Servlets correctly.

Right now the first part of sample code. This is my sample servlet that produces some basic JSON javascript

public class MySampleServlet extends HttpServlet
{
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
     {
         doPost(req, resp);
     }
 
     /* (non-Javadoc)
      * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
      @Override
      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException    
      {
 
          String asyncCallback = req.getParameter("callback");
          String output = asyncCallback+"([{color: \"red\",value: \"#f00\"}])";
 
          resp.setContentType("text/javascript");
          resp.addHeader("Pragma", "no-cache");
          resp.setStatus(200);
 
          PrintWriter out = resp.getWriter();
 
          out.println(output);
      }
}

You just then need to map that servlet in your web.xml. For the user of this example mine is mapped to testServlet.

What that outputs is something like this:

 
_gwt__callback0([{
                    color: "red\",
                    value: "#f00"
               }]);

The crucial part of this output is the _gwt__callback0 this identifies the Javascript callback function and I’ll explain where that is used.

Now back to the client side code, this is taken a lot from Dan’s article and isn’t really covering any new ground that he hasn’t already covered. This code makes the XSS call when a button is clicked.

The code is below:

// Used to identify the particular callback
 protected HashMap<Integer, Object> callbacks = new HashMap<Integer, Object>();
 protected HashMap<String, Element> scriptTags = new HashMap<String, Element>();
 
 protected int curIndex = 0;
 
public void someMethod()
{
    but.addClickListener(new ClickListener()
    {
        public void onClick(Widget arg0)
        {
            String serverURL = "http://localhost:8080/myApp/testServlet?callback=";
            String callbackName = reserveCallback();
            setup(this, callbackName);
            addScript(callbackName, serverURL + callbackName);
        }
    });
}
 
 public String reserveCallback() 
 {
     while (true) 
     {
      if (!callbacks.containsKey(new Integer(curIndex))) 
      {
       callbacks.put(new Integer(curIndex), null);
       return "__gwt_callback" + curIndex++;
      }
     }
 }
 
 /**
  * <p>Adds the JSONP script to our widget so we can make XSS requests baby</p>
  * 
  * @param uniqueId The unique id of the call
  * @param url The URL of our Request
  */
 public native void addScript(String id, String url) 
 /*-{
     var elem = document.createElement("script");
     elem.setAttribute("language", "JavaScript");
     elem.setAttribute("src", url);
     elem.setAttribute("id", id);
     document.getElementsByTagName("body")[0].appendChild(elem);
 }-*/;
 
 public void handle(JavaScriptObject jso) 
 {
     if( jso != null )
     {
      Window.alert("Woohoo JSO is not null it bloody worked");
     }
 }
 
 
 
 
 /**
  * 
  * <p>Sets up our Javascript cross site JSON call</p>
  * 
  * @param model Handles our Cross Site JSON call
  * @param callback
  */
 public native static void setup(MyClass myClass, String callback) 
 /*-{
      window[callback] = function(someData) 
      {
         myClass.@com.myCompany.MyClass::handle(Lcom/google/gwt/core/client/JavaScriptObject;)(someData);
      }
   }-*/;

Right now this needs describing as the syntax is a bit confusing. I’ll go through the onClick method and what it does bit by bit. First the server URL this is obvious and can be any server URL as you would expect. I simply use my app name (in this example myApp) and the servlet mapping as we mentioned earlier to be testServlet. One final thing on the URL is the callback parameter this is very important. If you look at our servlet we extract that and put it in our JSON output.

What the parameter does is basically allow the code to idenitify which method to call on the way back. The callback.

Next we set up the callback name nothing to exciting there it basically sets up a callback name based upon the number of calls – first one being _gwt_callback0, second _gwt__callback1 etc etc. We add this to a callbacks hash map for reference later if required.

The next very important method is setup(). This sets up the Javascript function that will be called on the callback and assigns it to the window object. We assign the function handle to the callback name ( initially __gwt_callback0 ) and the function it will call is the handle method in that class.

The final method is the addScript method this basically does what it says and adds some <script> tags to our page.

Now because those tags get executed the browser sees that it needs to fetch some content from a server and it goes off and fetches that content. Thats a key point that the browser realises it must go off and make the call. Its basically like any web developer would do when they include script say in their head file the page/browser knows that it must go off and fetch that script so it does. In our case it goes off and fetches the script from our Java Servlet.

Ok the final part of the understanding is how the system knows what to do when coming back from the server. If we take a look at the setup method you can see that we assigned the window[callback] to a function the function actually calls our handle method.

Now that callback would have been something like __gwt_callback0 with the number incrementing for every callback. So when we send over our JSON content, as described above, it invokes the function matching the name __gwt_callback0 and that actually invokes our handle method. Thats why the function name is so important.

(At this stage its worth pointing out that I’m making a big thing about the function name because that was the key part I missed in my understanding and AdamT cleared that up so you may have already got that ;) )

Thats pretty much it. You can then do what you like with the JavascriptObject that is passed back into the handle method. Dans article has a brief example of that.

Hope this all makes sense to people and will help describe JSONP and GWT somewhat. If people have questions please post as a comment so that everyone can see useful answers.

Categories: Google Web Toolkit Tags: