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 {
 }
 
}

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.