且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

序列化策略更改时,GWT客户端未收到IncompatibleRemoteServiceException

更新时间:2022-10-15 14:18:29

面向这个问题的解决方案:



首先扩展 RemoteServiceServlet (所有您的服务servlet将从这个新类扩展,记住这个是服务器代码)。这是相关代码:

  @Override 
protected SerializationPolicy doGetSerializationPolicy(
HttpServletRequest request,String moduleBaseURL,String strongName){
SerializationPolicy sp = super.doGetSerializationPolicy(request,
moduleBaseURL,strongName);
if(sp == null){//没有找到政策,可能是错误的客户端版本
throw new InvalidPolicyException();
}
return sp;
}

  @Override 
protected void doUnexpectedFailure(Throwable e){
if(e instanceof InvalidPolicyException){
sendError(); //发送消息重新加载客户端(错误的客户端版本)
return; //这就是
}
super.doUnexpectedFailure(e);
}

私有方法 sendError()基本上是GWT 500的错误代码的自定义副本(对于丑陋的Google缩进而言)

  private void sendError() {
ServletContext servletContext = getServletContext();
HttpServletResponse response = getThreadLocalResponse();
try {
response.setContentType(text / plain);
response.setStatus(HttpServletResponse.SC_CONFLICT);
try {
response.getOutputStream()。write(wrong client version.getBytes(UTF-8));
} catch(IllegalStateException e){
//处理以前使用getWriter()的(意外)情况
response.getWriter()。write(wrong client version);
}
} catch(IOException ex){
servletContext.log(
sendError在将以前的自定义失败发送给客户端时失败,ex);
}
}

我使用 HttpServletResponse.SC_CONFLICT (409),但您可能会使用一个聪明的错误代码。所写的消息并不重要。



然后在您的自定义 RpcRequestBuilder (这是客户端代码) p>

  public class CustomRequestBuilder extends RpcRequestBuilder {

private class RequestCallbackWrapper implements RequestCallback {

私人RequestCallback回调;

RequestCallbackWrapper(RequestCallback aCallback){
this.callback = aCallback;
}

@Override
public void onResponseReceived(请求请求,响应响应){
if(response.getStatusCode()== 409){//错误的客户端版本
Navigator.closeEveryPopUp();
Navigator.showUncloseablePopUp(再次登录!
new ClickHandler(){

@Override
public void onClick(ClickEvent event){
/ / reload your $#%^ client !!!
Window.Location.reload();
}
});
} else {
//(...)irrelevant security code here(...)
callback.onResponseReceived(request,response);
}

}

@Override
public void onError(请求请求,Throwable异常){
callback.onError(请求,异常);
}

}

@Override
protected void doFinish(RequestBuilder rb){
//(...)更多无关的安全性代码在这里(...)
super.doFinish(rb);
rb.setCallback(new RequestCallbackWrapper(rb.getCallback()));
}
}

我们使用多个弹出窗口,这就是导航器的一个原因类;当然在这里使用你自己的风格来警告用户。



编辑:这里发生了什么?



直到GWT 1.3.3 IsSerializable 是唯一可用于将类标记为GWT RPC serializabled的接口。接下来的版本接受了Java标准 Serializable 接口,但是为实现此接口的对象添加了安全策略的要求。默认情况下,GWT使用唯一的哈希名称为每个编译生成一个策略。一个旧客户端尝试传输标记为 Serializable 的对象将在服务器端抛出一个序列化策略异常,该异常将作为通用运行时错误在客户端接收。 IsSerializable 允许旧客户端仍然使用新服务,只要签名保持不变即可。这意味着这个问题的替代解决方案是将通过GWT RPC的每个对象标记为 IsSerializable 。但是,如果由于某些原因您需要您的对象不被引用到GWT界面,这对于旧的客户端连接来说是一个很好的解决方案。



检查 GWT的指南有关1.3.3后备的更多细节。


When the new version of app is deployed with changes to the model classes (for example adding/removing fields ). Client who has old version running gets com.google.gwt.user.client.rpc.SerializationException with the RPC made with the old client code and this is expected behavior. As a result we expect to see IncompatibleRemoteServiceException on the client side. However we get StatusCodeException.

StatusCodeException is 500 error and we can't customize the client side behavior easily (we don't want to assume every StatusCodeException or 500 error is a new version) . What could we be doing wrong here?

Note: On the server side (log) we get SerializationExcepion obviously since the serialization policy from the old client is no longer valid with the new server. So Why not throwing IncompatibleRemoteServiceException?

Thanks.

Here's my solution facing that issue:

First extend RemoteServiceServlet (all your service servlets will extend from this new class, remember this is server code). This is the relevant code in there:

@Override
protected SerializationPolicy doGetSerializationPolicy(
        HttpServletRequest request, String moduleBaseURL, String strongName) {
    SerializationPolicy sp = super.doGetSerializationPolicy(request,
                                   moduleBaseURL, strongName);
    if(sp == null) { //no policy found, probably wrong client version
        throw new InvalidPolicyException();
    }
    return sp;
}

and

@Override
protected void doUnexpectedFailure(Throwable e) {
    if(e instanceof InvalidPolicyException) { 
        sendError(); //send message to reload client (wrong client version)
        return; //that's it
    }
    super.doUnexpectedFailure(e);
}

The private method sendError() is basically a customized copy from GWT 500 error code (sorry for the ugly Google indentation)

private void sendError() {
    ServletContext servletContext = getServletContext();
    HttpServletResponse response = getThreadLocalResponse();
    try {
          response.setContentType("text/plain");
          response.setStatus(HttpServletResponse.SC_CONFLICT);
          try {
            response.getOutputStream().write("wrong client version".getBytes("UTF-8"));
          } catch (IllegalStateException e) {
            // Handle the (unexpected) case where getWriter() was previously used
            response.getWriter().write("wrong client version");
          }
        } catch (IOException ex) {
          servletContext.log(
              "sendError failed while sending the previous custom failure to the client", ex);
        }
}

I used HttpServletResponse.SC_CONFLICT (409) but you can probably use a clever error code. The message written is not really important.

Then in your custom RpcRequestBuilder (this is the client code)

public class CustomRequestBuilder extends RpcRequestBuilder {

 private class RequestCallbackWrapper implements RequestCallback {

        private RequestCallback callback;

        RequestCallbackWrapper(RequestCallback aCallback) {
            this.callback = aCallback;
        }

        @Override
        public void onResponseReceived(Request request, Response response) {
            if(response.getStatusCode() == 409) { //wrong client version
                Navigator.closeEveryPopUp();
                Navigator.showUncloseablePopUp("Login again!", 
                                new ClickHandler() {

                    @Override
                    public void onClick(ClickEvent event) {
                        //reload your $#%^ client!!!
                        Window.Location.reload();
                    }
                });
            } else {
                //(...)irrelevant security code here(...)
                callback.onResponseReceived(request, response);
            }

        }

        @Override
        public void onError(Request request, Throwable exception) {
            callback.onError(request, exception);
        }

 }

 @Override
 protected void doFinish(RequestBuilder rb) {
    //(...)more irrelevant security code here(...)
    super.doFinish(rb);
    rb.setCallback(new RequestCallbackWrapper(rb.getCallback()));
 }
}

We use multiple popups, that's one reason for the Navigator class; of course use your own style there to warn the user.

EDIT: What's going on here?

Until GWT 1.3.3 IsSerializable was the only interface available to mark a class as GWT RPC serializabled. The next version accepted Java standard Serializable interface for the same purpose, but adding the requirement of a security policy for objects implementing this interface. By default GWT generate a policy for each compilation with a unique hash name. An old client trying to transfer object marked as Serializable will throw at server side a serialization policy exception that will be received at client side as a generic runtime error. IsSerializable allows the old clients to still use the new services as long as the signature remains the same. This means that an alternate solution for this question is to mark every object going thru GWT RPC as IsSerializable. But if for some reason you need your objects not to be referenced to a GWT interface, this is a nice solution for old clients connections.

Check GWT's guide for more details regarding the 1.3.3 fallback.