Inline GWT serialized data with direct-eval RPC

May 1st, 2010 Alex Moffat Posted in EffectiveGWT, GWT | 2 Comments »

Earlier in the year I wrote about my experiences with using Inline GWT serialized data for reduced page load time. I recently tried out the GWT 2.0 direct-eval RPC implementation to see what impact it had in the same situation. First the numbers and then some information on the implementation. I recorded average deserialization timings and payload size using IE6, IE8, Chrome and Firefox 3.6 for one of the largest example processes I have in Blueprint.

RPC

  IE6     391ms 170kb
  IE8     198ms 170kb
  Chrome   31ms 170kb
  Firefox  80ms 170kb

Direct-eval RPC

  IE6     142ms 301kb
  IE8      95ms 301kb
  Chrome  159ms 301kb
  Firefox  76ms 301kb

As you can see the best saving is about 2 tenths of a second, for IE6, while Chrome is actually about 1 tenth of a second slower, which I was not expecting. The payload size just about doubles, as expected from the GWT documentation. Based on this and the advice in the documentation not to use this experimental code in production yet I decided to stick with our current SerializationPolicy based system.

The code on the client side is the same as when you’re using the previous serialization implementation. GWT.create can create the correct sort of serialization factory because ClientDataService implements the RpcService marker interface instead of RemoteService. In the example code below a ClientDate object is deserialized from a string embedded in the HTML page. The calls to Doings.mark and Doings.measure are to record the time take to do the deserializaton.


    public void loadRepository(String serializedData) {
        SerializationStreamFactory factory = GWT.create(ClientDataService.class);
        try {
            // Decode the data
            Doings.mark("deserialize");
            SerializationStreamReader reader = factory.createStreamReader(serializedData);
            clientData = (ClientData) reader.readObject();
            Doings.measure("deserialize");
        } catch (SerializationException e) {
            ClientRepositoryManager.logError("Error loading the repository!", e);
        }
    }

On the server the code is more complex. The basic idea is the same as I described before in the earlier post, you need to create an instance of some class using information produced at compile time and use that as a parameter to a method to serialize the data. When you’re using direct-eval RPC the class you need an instance of is ClientOracle. Once you’ve got that you can call streamResponseForSuccess on RPC. The example code below serializes a ClientData object.


        ClientData data = /* build data */

        // Encode the data to send to the client
        ClientOracle oracle =
            serviceManager.clientOracleProvider(request).getClientOracleFor(request, page);

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        RPC.streamResponseForSuccess(oracle, os, data);

        String payload = "";
        try {
            payload = os.toString("UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("UnsupportedEncodingException converting json.");
        }

The tricky bit is constructing the ClientOracle and it’s tricky for the same reasons constructing a SerializationPolicy is, you need to use information created at compile time that is normally accessed at run time, and when it’s normally accessed it uses information passed from the client to the server. For direct-eval RPC what you need is the strong name for the permutation that you are going to generate the data for. You can see how this is done if you look at the code for RpcServlet. The data for the ClientOracle is in a file named <strong name>.gwt.rpc. This is in contrast to the SerializationPolicy case where you have a file per RemoteService interface instead of a file per permutation. Unfortunately the permutation is chosen on the client, not on the server, so there is no ready made code to pick the permutation that can be used on the server. You can’t, of course, wait for the client to determine the value because that defeats the whole purpose of this work, avoiding a roundtrip.

My solution uses the symbol maps produced when you pass the -extras flag to the GWT compile. There is one symbol map for each permutation and the name of the symbol map file is <strong name>.symbolMap. The useful thing about the symbol map for my purposes is that at the top of it is information about which values of which deferred binding properties apply to that map. So, by reading the top of the map you can work out which strong name goes with which deferred binding property values. Here’s the first 4 lines from one of the symbol map files. This one is for the permutation used by ie6.

  # { 0 }
  # { 'user.agent' : 'ie6' }
  # jsName, jsniIdent, className, memberName, sourceUri, sourceLine
  KMf,,boolean[],,Unknown,0

As part of the build process I create a mapping from property values to strong name and record this in a file. Then, if you can calculate the values of the binding properties on the server for a particular request, you can choose the correct strong name value and therefore pick the correct .gwt.rpc file. As part of the application initialization a client oracle provider object is configured with the information from the file. When a ClientOracle is requested at runtime the correct property values are calculated from the incoming HTTP request and the appropriate ClientOracle is returned. Caching is used as a single oracle can serve multiple threads.

2 Responses to “Inline GWT serialized data with direct-eval RPC”

  1. [...] This post was mentioned on Twitter by ludovic meurillon. ludovic meurillon said: Inline GWT serialized data with direct-eval RPC http://bit.ly/aCfTvi [...]

  2. Sean Stephenson Says:

    Very nice article. It definitely helped me get started in the right direction. I was able to get the strong name on the server without emitting the entire symbol map by making a custom GWT linker and overriding the doEmitCompilation method. Here’s my implementation.

    protected Collection doEmitCompilation(TreeLogger logger, LinkerContext context, CompilationResult result) throws UnableToCompleteException {
    Collection artifacts = super.doEmitCompilation(logger, context, result);
    artifacts.add(emitPermutationProperties(logger, result));
    return artifacts;
    }

    private EmittedArtifact emitPermutationProperties(TreeLogger logger, CompilationResult result) throws UnableToCompleteException {
    // Turn the deferred binding properties into an actual Properties object for serialization
    Properties properties = new Properties();
    for (SortedMap map : result.getPropertyMap()) {
    for (Map.Entry entry : map.entrySet()) {
    SelectionProperty property = entry.getKey();
    properties.put(property.getName(), entry.getValue());
    }
    }
    StringWriter output;
    try {
    output = new StringWriter();
    properties.store(output, getCompilationStrongName(result));
    } catch (IOException e) {
    System.out.println(”Could not complete generate permutation properties”);
    e.printStackTrace();
    throw new UnableToCompleteException();
    }

    return emitString(logger, output.getBuffer().toString(), getCompilationStrongName(result) + “.permutation”);
    }

    So it basically emits a properties file with the deferred binding properties for each permutation as a separate artifact.

Leave a Reply