如何扩展GWT的RPC序列化机制?

时间:2017-05-02 22:27:44

标签: serialization gwt rpc

我理解,在GWT的RPC上,使用ClientSerializationStreamWriter在客户端上序列化参数,并使用ServerSerializationStreamReader在服务器上反序列化,另一方面,使用ServerSerializationStreamWriter在服务器上序列化返回值并反序列化在客户端上使用ClientserializationStreamReader。

我试图在发件人方面计算一个校验和或类似的东西,它基于序列化参数(客户端 - >服务器)或返回值(服务器 - >客户端)并添加它到序列化流。在接收方的一侧,校验和从序列化和接收的参数或返回值(当然没有额外的校验和)重新计算,并与接收的校验和进行比较。

为了使校验和计算与具体参数/返回值类型无关,我更倾向于将计算基于序列化值而不是原始类型。这将节省我对对象图的额外遍历,从而解决另一个问题:考虑对象图中的所有值,而不可能依赖Java的反射API(客户端上不可用)。

我首先考虑引入参数对象(由任何给定RPC的实际参数组成)和返回值包装器,然后为它们引入自定义字段序列化器。在序列化时,我可以首先序列化参数/返回值,从* SerializationStreamWriter访问序列化值,计算校验和并使用相同的* SerializationStreamReader写入校验和。然而,在接收器端,似乎我无法访问序列化值而没有使用* SerializationStreamReader实际读取它们,因为在调用第一个自定义字段序列化器时序列化流已经被标记化。

所以问题是:

  1. 我错过了一个钩子吗?或
  2. 是否有一种有效的方法可以在客户端和服务器上以相同的方式(相同的格式)序列化对象图,而无需实现特定于具体参数或返回值类型的任何内容? / LI>

1 个答案:

答案 0 :(得分:0)

Actually Thomas (see comment added to question) did answer the question. @Thomas: thanks, that was exactly what I missed in my previous Research.

I can add some details to it.

As proposed by Thomas, on the client side a custom RpcRequestBuilder Needs to be set on the async service Interface:

IMyServiceAsync service = GWT.create(IMyService.class);
((ServiceDefTarget)service).setRpcRequestBuilder(new MyCustomRpcRequestBuilder());

The custom RpcRequestBuilder needs to override doSetRequestData to manipulate the request that is about to be sent and doSetCallback to get its hands on the response before it is processed:

private final class MyCustomRpcRequestBuilder extends RpcRequestBuilder {
  @Override
  protected void doSetRequestData(RequestBuilder rb, String payload) {
    // do whatever you need to do on the payload, request header, ...
    String checksum = calculateChecksum(payload);
    rb.setHeader(MY_CUSTOM_HEADER_NAME, checksum);
    super.doSetRequestData(rb, payload);
  }

  @Override
  protected void doSetCallback(RequestBuilder rb, final RequestCallback callback) {
    super.doSetCallback(rb, new RequestCallback() {
      @Override
      public void onResponseReceived(Request request, Response response) {
        // do whatever you need to do on the response header, ...
        String receivedChecksum = response.getHeader(MY_CUSTOM_HEADER_NAME);
        if (receivedChecksum.equals(calculateChecksum(response.getText())) {
          callback.onResponseReceived(request, response);
        }
        else {
          callback.onError(request, new InvalidChecksumException(...));
        }
      }

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

On the server side a servlet filter can be used. GWT's RPCServletUtils has some useful methods herefore:

public class MyFilter implements Filter {  
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
  }

  @Override
  public void destroy() {
  }

  @Override
  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest httpReq = (HttpServletRequest)req;
    HttpServletResponse httpResp = (HttpServletResponse)resp;

    String receivedChecksum = httpReq.getHeader(MY_CUSTOM_HEADER_NAME);
    String reqPayload = RPCServletUtils.readContentAsGwtRpc(httpReq);
    if (receivedChecksum.equals(calculateChecksum(reqPayload)) {
      final ServletInputStream inputStream = new ByteArrayServletInputStream(reqPayload.getBytes(RPCServletUtils.CHARSET_UTF8));
      HttpServletRequestWrapper reqWrapper = new HttpServletRequestWrapper(httpReq) {
        @Override
        public ServletInputStream getInputStream() throws IOException {
          return inputStream;
        }
      };
      final CachingServletOutputStream outputStream = new CachingServletOutputStream();
      HttpServletResponseWrapper respWrapper = new HttpServletResponseWrapper(httpResp) {
        @Override
        public ServletOutputStream getOutputStream() throws IOException {
          return outputStream;
        }
      };

      filterChain.doFilter(reqWrapper, respWrapper);

      // possibly you also need to handle gzipped payload
      String respPayload = new String(outputStream.getCachedDataAsBytes(), RPCServletUtils.CHARSET_UTF8);
      String checksum = calculateChecksum(respPayload);
      httpResp.setHeader(MY_CUSTOM_HEADER_NAME, checksum);
      httpResp.getOutputStream().write(outputStream.getCachedDataAsBytes());
    } else {
      httpResp.setContentType("text/plain");
      httpResp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
      httpResp.getOutputStream().write("Invalid checksum".getBytes(RPCServletUtils.CHARSET_UTF8));
    }
  }

  static class ByteArrayServletInputStream extends ServletInputStream {
    private final InputStream delegate;

    ByteArrayServletInputStream(byte[] data) throws IOException, ServletException {
      delegate = new ByteArrayInputStream(data);
    }

    @Override
    public int read() throws IOException {
      return delegate.read();
    }
  }

  static class CachingServletOutputStream extends ServletOutputStream {
    private final ByteArrayOutputStream cache;

    CachingServletOutputStream() {
      this.cache = new  ByteArrayOutputStream(4096);
    }

    @Override
    public void write(int b) throws IOException {
      cache.write(b);
    }

    @Override
    public void flush() throws IOException {
      cache.flush();
    }

    byte[] getCachedDataAsBytes() {
      return cache.toByteArray();
    }
  }
}