Hijacking GWT-RPC serialization

GWT, the Google Web Toolkit for client-side web programming, contains several client/server communication frameworks. The « traditional » one is called GWT-RPC. GWT-RPC encapsulates the serialization/deserialization of objects, and all the protocol plumbing.

This is usually fine for most use cases, but I needed to explicitely serialize objects on the client side, and deserialize them on the server side.

Use case : I need to call a servlet from my GWT application, to open a document (generated server-side) in a new windows – using window.open()

Solution : create a standard GWT-RPC, declaring a « fake » method containing all the parameter types that we need to explicitly serialize. This will generate a correct GWT « serialization policy ». Then, server side, implement the GWT Service servlet, and override the doGet with your code.

Create the standard GWT-RPC interfaces :

The interface declares a fake method, with the only goal to allow the type we want to add in the serialization process (here, we have OutputTypeEnum, RepositoryServiceEnum and FBBaseFilterPagingLoadConfig).

@RemoteServiceRelativePath("../fastbiz/service/recordUpload")
public interface RecordUploadService extends RemoteService {
 // Fake
 void fakeModel(OutputTypeEnum outputType, RepositoryServiceEnum repository, FBBaseFilterPagingLoadConfig config) throws FBException;
}

 
public interface RecordUploadServiceAsync {
 // Fake
 void fakeModel(OutputTypeEnum outputType,RepositoryServiceEnum repository,FBBaseFilterPagingLoadConfig config, AsyncCallback<Void> callback);
}

  • Client side :

// Creating factory. Interface of any remote service can be used
SerializationStreamFactory factory = (SerializationStreamFactory) GWT.create(RecordUploadService.class);

// Creating stream writer
SerializationStreamWriter writer = factory.createStreamWriter();

// Serializing object
String serializedOutputType = null;
String serializedRepositoryIdentifier = null;
try {
 writer.writeObject(outputType);
 // Getting serialized object content
 serializedOutputType = URL.encodeQueryString(writer.toString());

 writer = factory.createStreamWriter();
 writer.writeObject(listWindow.getRepositoryIdentifier());

 // Getting serialized object content
 serializedRepositoryIdentifier = URL.encodeQueryString(writer.toString());
 
 writer = factory.createStreamWriter();
 PagingLoadConfig config = listWindow.getConfig();
 writer.writeObject(config);
} catch (SerializationException e) {
 e.printStackTrace();
}

// Getting serialized object content
String serializedConfig = URL.encodeQueryString(writer.toString());
 
Window.open(Upload.EXPORT_BASE_URL + "?" + Upload.PARAM_OUTPUT_TYPE + "="
 + serializedOutputType + "&"
 + Upload.PARAM_REPOSITORY_IDENTIFIER + "="
 + serializedRepositoryIdentifier + "&"
 + Upload.PARAM_PAGING_LOAD_CONFIG + "="
 + serializedConfig, "_blank", null);

 

  • Server side :

You implement the service interface, and override the GET method that way:

 

public class RecordUploadServiceImpl extends RemoteServiceServlet implements com.fastbiz.client.service.RecordUploadService {
 
 private static final long serialVersionUID = 9107309941962048452L;
 
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
 
 // Ensure the thread-local data fields have been initialized
 
 try {
  // Store the request & response objects in thread-local storage.
  synchronized (this) {
   if (perThreadRequest == null) {
    perThreadRequest = new ThreadLocal<HttpServletRequest>();
   }
   if (perThreadResponse == null) {
    perThreadResponse = new ThreadLocal<HttpServletResponse>();
   }
   perThreadRequest.set(request);
   perThreadResponse.set(response);
  }
  processGet(request, response);
 
 } catch (Throwable e) {
  // Give a subclass a chance to either handle the exception or
  // rethrow it
  doUnexpectedFailure(e);
 } finally {
  // null the thread-locals to avoid holding request/response
  perThreadRequest.set(null);
  perThreadResponse.set(null);
 }
 
 }
 
 protected void processGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
  if (Upload.TYPE_LIST.equals(origin)) {
 
  // Initializing stream reader
  ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(
  Thread.currentThread().getContextClassLoader(), this);
  RepositoryServiceEnum recordType;
  FilterPagingLoadConfig config;
  OutputTypeEnum outputType;
  try {
   // Filling stream reader with data
   streamReader.prepareToRead(request.getParameter(Upload.PARAM_OUTPUT_TYPE));
   // Reading deserialized object from the stream
   outputType = (OutputTypeEnum) streamReader.readObject();
  } catch (SerializationException e1) {
   throw new ServletException("Unable to deserialize parameters",
   new FBException(e1));
  }
  try {
   // Filling stream reader with data
   streamReader.prepareToRead(request.getParameter(Upload.PARAM_REPOSITORY_IDENTIFIER));
   // Reading deserialized object from the stream
   recordType = (RepositoryServiceEnum) streamReader.readObject();
  } catch (SerializationException e1) {
   throw new ServletException("Unable to deserialize parameters",
   new FBException(e1));
  }
  try {
   // Filling stream reader with data
   streamReader.prepareToRead(request.getParameter(Upload.PARAM_PAGING_LOAD_CONFIG));
   // Reading deserialized object from the stream
   config = (FilterPagingLoadConfig) streamReader.readObject();
  } catch (SerializationException e1) {
   throw new ServletException("Unable to deserialize parameters",
   new FBException(e1));
  }
 
 }
 
}
 
 @Override
 public void fakeBaseModel(OutputTypeEnum outputType, RepositoryServiceEnum repository, FBBaseFilterPagingLoadConfig config) throws FBException {
 }
 
}

GWT et encoding UTF-8

Le framework ajax GWT est conçu pour fonctionner avec des données UTF-8. Cela peut avoir quelques conséquences pour les applications internationales au niveau des caractères spéciaux (accents français par exemple…).

Ayant butté longuement sur un problème de cette nature, je vous en livre la solution ici.

Mon application GWT réalise des appels RPC sur un serveur Tomcat. La première chose à faire, comme cela est bien précisé sur divers sites, est d’encoder systématiquement tous les fichiers sources de votre application en UTF-8, et non pas en Cp1252 par exemple. Dans Eclipse : click droit sur le projet dans le Package Explorer, puis Properties, puis Resource, sélectionnez Text file encoding : Other : UTF-8. Sur un autre IDE ou éditeur de fichier, vous trouverez l’équivalent.

La deuxième chose à faire, concerne la compilation du code pour le serveur (pour les servlets appelées par GWT) : il faut préciser javac -encoding utf8

Personnellement, j’utilise ant pour mes compilations, et donc la commande suivante :

<javac srcdir="${src.dir}" destdir="${dest.dir}" classpathref="server.lib" mce_href="server.lib" encoding="UTF-8">
<include name="..." />
</javac>

RSS
LinkedIn
Share