Sunday, February 18, 2007

GWT-RPC: Customizing RPC in GWT

In the unlikely event that you were not aware, the GWT developers are hard at work on a 1.4 release. This release, like many software projects, is going to miss it's release date, but now that GWT is open source we can get a peek at the code that is coming our way soon. In this article I want to take a look at the new and improved RemoteServiceServlet. If you simple use GWT-RPC, then the change will likely be unnoticeable, as it should, but if you have been struggling to customize GWT-RPC, then the changes are exactly what the doctor ordered.

Who, What, and Why?

Before we get into the changes, I would like provide some history for this change so that we can better understand why this change is so important. Back on November 10th, George Georgovassilis posted bug report 389, which suggested changes for making the GWT-RPC mechanism easier to extend. If you aren't familiar with George's work, he is responsible for the GWTSpringController from the GWT-SL project, allowing a tight integration of GWT and Spring. The problem is that the current RemoteServiceServlet is nearly impossible to extend in a clean way, so George ended up needing to use CGLib, a code generation library that allows you to create classes at runtime. George's code does the trick, but it should be a lot easier to extend the RemoteService Servlet.

This bug report received very little attention until this post on the GWT Contributors list from Rob Jellinghaus.

Date: Fri, Jan 5 2007 10:23 pm
From: "Rob Jellinghaus"

Issue 389
http://code.google.com/p/google-web-toolkit/issues/detail?id=389 is listed as "low" priority, assigned to gwt.team.mmendez.

I am interested in working on this in order to facilitate a better GWT-to-JSF coupling. (Basically I want to be able to specify a JSF managed bean as the service endpoint for a GWT component via JSF, without needing to write a per-bean RemoteServiceServlet, or do CGLIB magic as in http://g.georgovassilis.googlepages.com/usingthegwthandler. Just checked out the GWT source with an eye towards making a patch to support this.

So my question is mainly for mmendez: is any active development underway on this that I am likely to collide with, or is this pretty much going to stay on the back burner for the next month or two? It'd be dispiriting to get it working only to find out someone else already got it into the latest release candidate :-)

Cheers!
Rob Jellinghaus

This got the ball rolling, and discussion ensued. Rob Jellinghaus did a bit more than just discuss the problem, he also coded the solution. So many thanks goes to Rob Jellinghaus for this patch that we should be seeing in the 1.4 release of GWT. So, not that we know the who, what, and why, lets take a look at the how.

RemoteServiceServlet - The OLD Way

As you already know, when your GWT code on the client calls the server, your custom servlet is executed, and your servlet extends the GWT RemoteServiceServlet. The execution starts with the doPost() method of the servlet being called, which in turn takes the serialized RPC request and passes it to processCall() for processing. The sequence diagram below shows the processing flow.



Inside of the processCall() method it does literally all of the work for handling the request. It passes the serialized request to the onBeforeRequestDeserialization(), allowing a subclass to perform some work on the serialized data prior to deserialization. It then checks that the target class implements the RemoteServiceInterface, deserializes the payload, invokes the RPC request, serializes the response, and finally calls onAfterResponseSerialized(). In short, the processCall() method does a LOT of work, allowing only a peek at the data before deserialization and after serialization of the response. It does not allow you to alter the process in any way unless you override and implement the entire processCall() method.

RemoteServiceServlet - The NEW Way

The changes to the RemoteServiceServlet in GWT 1.4 won't change the way you use GWT-RPC, but it does add a lot opportunities for customizing the handling of the RPC request. The sequence diagram below shows part of the picture. The two things that stand our are that the onBeforeRequestDeserialized() and onAfterResponseSerialized() methods are now outside of the processCall() method, and that there is now a new RPC class.




By moving the two "peek" methods out of processCall(), and moving all of the logic into a new RPC class simplifies the processCall() greatly. So great is this reduction that processCall() only consists of two lines of code! Below is the new processCallMethod().


public String processCall(String payload)
throws SerializationException {
RPCRequest rpcRequest = RPC.decodeRequest(payload, this.getClass());
return RPC.invoke(this, rpcRequest.getMethod(), rpcRequest.getParameters());
}


There are a few key points about this new mechanism.

1. All of the logic has been moved into public static methods of the RPC class. This makes it possible to write your own service that doesn't use the RemoteServiceServlet at all.

2. None of the methods in RPC access the HttpServletRequest or HttpServletResponse object. Besides simplifying testing, you could in theory write a service that doesn't even use a servlet container. In theory you could write a service that works over email, FTP, telnet, or pretty much anything. Any volunteers to be the first to try sending GWT-RPC messages over email?

3. The processCall() method is only two lines of code. This allows you to override the processCall() method without the hacking that was required previously.

Now lets take a closer look at the methods in the RPC class.

The New RPC Class

There are only a few public methods in the new RPC class, so we might as well look at them all.

public static RPCRequest decodeRequest(String payload, Class service)

You saw this method above in the processCall() method. You pass in the serialized RPC request, and the class that will accept the call. As a security precaution, the method will check that the service class implements the expected service interface as well as the RemoteService interface. The method then deserializes the request and returns a RPCRequest object. The RPCRequest class is new, and contains a Method object and and Object array of parameters. The Method class is part of Java's reflection API, and this object can be used to invoke the method.

public static RPCRequest decodeRequest(String payload)

This is the same as the first version of decodeRequest(), except that it will skip the check to see if the target service class implements the proper interface. Internally, calling this method is the same as calling decodeRequest(payload, null).

public static String invoke(Object target, Method serviceMethod, Object[] parameters)

The invoke method executes the method and returned a serialized response. The target is the object that the method will be called on. In the default processCall() method, that we saw in the code snippet above, the "this" object is passed, meaning that the servlet itself must implement the method. But because you can pass the object to the invoke method you could delegate the RPC call to some other class. This could be useful in frameworks like Spring, where you want to use dependency injection to allow for swapping out implementations. The serviceMethod parameter is the Method object that will be called, and parameters is an array of Objects that will be passed to the method.

public static String encodeSuccess(Method serviceMethod, Object returnValue)

The encodeSuccess() method is used internally by the invoke() method that we just discussed. Simply put, it takes the Method object that was invoked, and the Object result of the invocation, and serializes the result. Because this method is broken out of the invoke() method it allows you to replace the invoke() method with your own, and still be able to use the encoding facilities.

public static String encodeFailure(Method serviceMethod, Throwable cause)

Again, the envodeFailure() method is used by invoke() above, but if you need to write your own invoke() method you can still use this method to serialize an error.

The Sum of the Changes

As you can see, the changes are completely backwards compatible, yet allow for ease of extension. You can customize the handling of the processing of requests by overriding the processCall() method to add code to alter the serialized response before processing, modifying the serialized response before it goes back to the client, alter the way the method is invoked, and even delegate execution to some other class. I look forward to seeing further GWT integration on the back-end with Spring, JSF, EJB, and everything in between.

2 comments:

Rob Jellinghaus said...

Thanks, Robert, this is a good and accurate writeup.

For the record, Miguel Mendez at Google was incredibly helpful and patient in developing this patch. The final design is as much his as it is mine, and without his repeated attention it would never have made it through the review process.

Anonymous said...

Thanks for the nice post!