In this post I will describe how we are using server side GWT RPC serialization at Ikayzo to both improve performance and simplify client code logic.
First, let's have a quick recap of the normal startup sequence of a GWT application. As depicted below, it begins with the client requesting an HTML page containing a GWT script reference and static application text. All that is required in this initial page is a one line script that includes the GWT application's "nocache" file. The job of the nocache file is to bootstrap the application by executing the correct chunk of compiled GWT application javascript, known as the "cache" file.
The cache file can be very large, but as its name implies, it should be cached by the client and is intended to be downloaded only once. (So it is not shown in the sequence above.) The tradeoff here is that by performing a quick check with every page load GWT achieves what Google terms "perfect caching".
Once the GWT application has started there are generally two ways that it can go about gathering its required data. The typical method is to make one or more GWT RPC calls back to the server, receiving the information in callbacks. Alternately, the app can accept limited attributes from its root panel HTML div tags or use JSNI calls to read arbitrary JavaScript variables from the page and browser environment.
Using GWT RPC is the canonical way to get data into a GWT app and generally yields stronger code because it stays in the realm of Java's static typing. (Presumably if you are using GWT you already understand these benefits, so I won't harp on this point.) However the alternative of simply reaching out into the page through JavaScript and grabbing what you need has a strong appeal. First, it is often easier to add some data to the HTML page and pluck it out on the client than to implement a new service method and handle the callback in your application code. Second, there is the obvious performance advantage of having the data ready to go versus making yet another call back to the server. The performance benefit can be especially poignant in this case because it's the first thing the user experiences when they load the page and wait for the app to do something useful.
Fortunately, there is a way to get the both benefits of GWT RPC's static typing and the performance gain and simplified logic of "ready to go" data stored in the page. The answer is to make one or more GWT RPC calls on the server side and serialize the results into the page as you would other javascript data. The client side GWT code will then pull them out of the page and deserialize the GWT object, just as if it had been received from the callback, enjoying all of the type safety without the round trip and asynchronous callback logic.
We used this technique extensively in a recent project and it has provided a lot of benefit for both the end user experience and maintenance of the client code. There are, however, tradeoffs in complexity on the server side and a few things that are a real hassle to deal with. In the rest of this post I'll share the snippets of code that will save you some time and outline some of the issues that still need to be addressed to make this easier in the future.
In our code we have created a number of convenience methods and built some of this into our base application layer. But the effect is that on the client side we can simply make a call like the following in our widget initialization code:
// Client Side - What we're going for...
Customer customer = getSerializedObject("customer");
The first step to making the above happen is to capture the Customer object on the server side while we are building the page. This implies of course that you have a presentation layer or servlet feeding your pages and that it knows what the client needs. We use Struts 2 as a "glue" layer to help us build our GWT pages and so in our case this server side data capture happens in a Struts action class.
Let's assume that we get our Customer from a GWT RPC service with a method called getCustomerById(). First we'll simply invoke the method on the server side. If you haven't done anything egregious in your service implementation then instantiating an instance of it should be a lightweight operation. (We use Spring to hold all of our controllers and setup). Alternately you can register your instance somewhere and reuse it. So we'll get the Customer object like this:
// Server Side - Get the customer object
long customerId = ...;
Customer customer = new MyServiceImpl().getCustomerById( customerId );
The next step is to serialize the object.
// Server Side - Serialize customer to a String
// ...
String serializedCustomer =
RPC.encodeResponseForSuccess( serviceMethod, customer, serializationPolicy );
The snippet above references our customer object as well as two new items that require explanation: The service method and serialization policy. The public API for serializing the object is the static method RPC.encodeResponseForSuccess(). This method requires a reference to the java.lang.reflect Method object representing our getCustomerById() interface method. It's not going to invoke the method (we've already done that), but it seems to require it simply to determine the declared return type. We can get the method reference like this:
// Get the Method for encodeResponseForSuccess()
Method serviceMethod = MyService.class.getMethod( "getCustomerById", Long.TYPE )
Here we are asking the MyService class for a reference to our method. The first argument to getMethod() is the name of the method and what follows is a varargs list of parameter types. In this case the special field Long.TYPE represents the primitive long type. (Long.class would represent the Long wrapper type). If there were additional arguments we'd list their types here.
Next there is the serialization policy. This is a bit of a headache. If you wish to simply tag your domain classes with java.io.Serializable (standard Java) and use them as is recommended with GWT going forward then you have to load a serialization policy that tells GWT which classes it is allowed to serialize. The serialization policy lists all of the application classes that you reference from the interface. GWT generates one of these files for each of your service interface at build time. The file is named
.gwt.rpc. The is the 32 character MD5 hash that is used to uniquely name files in the GWT build. You must identify this file and load it using the SerializationPolicyLoader. Here we have copied the file to the root of our classpath and renamed it simply "gwt.rpc". Please note that this file must be deployed to the server side classpath in this scenario:
// Server side - Get the SerializationPolicy for encodeResponseForSuccess()
// Note: You should cache the SerializationPolicyInputStream in =
GadgetAction.class.getResourceAsStream( "/gwt.rpc" );
SerializationPolicy serializationPolicy =
SerializationPolicyLoader.loadFromStream( in );
The difficulty I alluded to can be seen above. I cannot tell you how to identify the correct .gwt.rpc file as it will have a unique name based on the contents. At the moment this is a manual step in our build when the interface changes. I can think of a number of hacky ways to automate this step, but as far as I know there are only two good solutions (neither of which I have tried yet). One would be to write a GWT linker that participates in the build and can easily export a copy of the file under the correct name. I'm sure someone will respond to this posting by telling me how easy that is to do and I hope they also send an example :) Another way would be to simply implement a custom SerializationPolicy that is aware our class structure in a broader way. But such as custom policy would have to be aware of which standard types are allowed to be serialized as well as which custom types. Getting this wrong would not be a security issue in this case, but would certainly cause failures at runtime.
Ok, the tricky part is over and now we have our serialized customer object as a string on the server side. The next step is to embed it into a javascript variable in the page. To do that we're just going to insert a javascript block and declare a variable, customer, with a single quoted string value of our serialized customer object. We use a Struts 2 expression to do this, but you could easily insert this with raw string replacement in a servlet or using any template tool:
Before doing that substitution though, there is one last thing you must do. Your serialized data may contain characters that would be misinterpreted inside a single quoted javascript string. This is especially true if your data contains text with markup or HTML. To sanitize it we created the following helper method, escapeForSingleQuotedJavaScriptString():
// Server Side String utils
public static String escapeForSingleQuotedJavaScriptString( String s )
{
s = escapeScriptTags( s ); //
Views: 13856
Tags:
MySpace Facebook
Facebook
You need to be a member of TechHui to add comments!
Join TechHui