New Code in Gwittir (svn)

Tagged:
So this is the first big new thing in a long time. It stems from some work I was just recently doing. So, basically there is a problem with GWT's basic RPC: If you are trying to send back large blocks of data or you are trying to deal with medium sized chunks of data on a machine with limited specs the cycle of "Read all data in on the server -> Serialize -> Send To Client -> Deserialize -> Render" can take large amounts of RAM on the server to deal with the request, and large amounts of time on the client to deserialize and set up the UI. Enter the StreamingService. The StreamingService works almost exactly like the standard RPC system, except results are sent back one at a time. To build a new system, you start much like you would with RPC, except the return type of all your methods should be a typed "StreamServiceIterator". This is a standard iterator with a close() method on it that will get called so you can clean up after you are done (close database connections, release entity managers, etc). In the example code, first the service def:

package com.totsp.gwittir.example.client;

import com.totsp.gwittir.client.stream.StreamingService;
import com.totsp.gwittir.client.stream.StreamServiceIterator;

public interface ExampleStreamService extends StreamingService {

    public StreamServiceIterator<MyClass> getResults(int count,
                     String name);

}
Then an Async version. The Async versions will return a StreamControl object. This object exposes only a .terminate() method right now. This allows you to cut off the stream early. It also, like the standard Async, takes a callback as the last argument.
package com.totsp.gwittir.example.client;

import com.totsp.gwittir.client.stream.StreamControl;
import com.totsp.gwittir.client.stream.StreamServiceCallback;

public interface ExampleStreamServiceAsync {

    public StreamControl getResults(int count, String name,
                               StreamServiceCallback callback);

}
Next, to call the stream service, it works much like the onSuccess()/onFailure() in RPC, except each individual object will give you a call into the code, and an onComplete() will be called when all the data has been sent.:
ExampleStreamServiceAsync ser = (ExampleStreamServiceAsync) GWT.create(ExampleStreamService.class);
        StreamingServiceStub stub = (StreamingServiceStub) ser;
        stub.setServicePath(GWT.getModuleBaseURL()+
                         "ExampleStreamService");
        ser.getStrings(5, "foo", 
          new StreamServiceCallback<MyClass>(){

            public void onReceive(MyClass object) {
                Window.alert("Got: "+object.getName());
            }

            public void onError(Throwable thrown) {
                Window.alert("Thrown: "+thrown.toString());
                GWT.log("", thrown);
            }

            public void onComplete() {
                Window.alert("complete.");
            }

        });
Finally, to build your server code, you extend the StreamServiceServlet, rather than RemoteServiceServlet, and implement your methods:
public class ExampleStreamServiceServlet extends StreamServiceServlet implements ExampleStreamService {

    public StreamServiceIterator<MyClass> getResults(
        final int count, final String name) {
        System.out.println( count + name );
        return new StreamServiceIterator(){
            int i=0;
            public boolean hasNext() {
                return i < count;
            }

            public MyClass next() {
                i++;
                MyClass mc = new MyClass();
                mc.setName(name + i);
                return mc;
               
            }

            public void remove() {
                throw new UnsupportedOperationException("Not supported yet.");
            }

            @Override
            public void close(){
                System.out.println("closed.");
            }

        };
    }

}
How does this whole thing work? Well, rather than use XHR and send all the data and get a single event back, it creates an invisible form and iframe on the page, and POSTs the request to the server. The server then uses a Comet-style callback from the iframe to send each serialized object into the application. Since the script thread releases after each one, and hopefully an individual one will go fairly fast, this lets you maintain UI usability while the data is still coming in from the server -- which is great on slow machines. It also doesn't require a "big create" to RAM, usually looping a Collection to create UI elements, meaning that unused objects that only go to display can fall away quickly. There are a few known bugs in it right now, but it is good for experimental use.

Comments

Interesting approach, but

Interesting approach, but this is only a solution for RPC calls that need to send back a large list of "smaller" objects. I need an improved RPC where complex objects can be serialized without stackoverflow or outofmemory problems. For that I really need an RPC that streams on byte level and not on object level. Deserializing in Javascript should also be done incrementally to avoid slow script warnings. These objects are not always "big" but JS has a real issue with it (just try a linked list implementation or a tree implementation, the RPC layer shokes very quickly). David

Don't you want too much from JS dealing with deserialization?

It seems to me that, all serialization-based stuff is just simply wrong approach for RPC solutions. Why not use conventions and something like JSON-RPC framework together with java-beans to send objects? It's much faster and uses much lesser memory amount. It would be great improvement, if Google seamlessly incorporates JSON-RPC with GWT, I think.

I think the point of GWT-RPC

I think the point of GWT-RPC is it is easier on bandwidth than JSON.

Clever

Hey Charlie, Interesting post! Thanks Luciano

It is an interesting post,

It is an interesting post, but I can't take any credit for it. Cooper is the man behind gwitter, and this post. Props to Coop.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.