【问题标题】:How can I fully intercept a gRPC java unary call, on the client, and on the server?如何在客户端和服务器上完全拦截 gRPC java 一元调用?
【发布时间】:2019-06-10 15:36:32
【问题描述】:

我正在将分布式系统代码库从 SOAP (JAX-WS) 迁移到 gRPC-java。 我们使用这个代码库来教授远程调用、容错、安全实现。

在 JAX-WS 架构上,有一个拦截器类(称为 SOAP 处理程序)可以拦截 SOAP 消息。您可以在客户端和服务器上配置处理程序。

作为参考,这是在 JAX-WS 上远程调用的完整序列:

  • 客户端 - 创建端口(存根)并调用远程方法
  • 存根 - 将 Java 对象转换为 SOAP 消息 (XML)
  • ClientHandler - 拦截传出的 SOAP 消息并可以对其进行读/写
  • 网络 - 发送的 SOAP 请求消息
  • ServerHandler - 拦截传入的 SOAP 消息,可以读/写
  • Tie - 将 SOAP 消息转换为 Java 对象
  • 服务器 - 执行方法,响应
  • ServerHandler - 拦截传出的 SOAP 响应,可以读/写
  • 网络 - 已传输 SOAP 响应消息
  • 客户端 - 创建端口(存根)并调用远程方法
  • 存根 - 将 Java 对象转换为 SOAP 消息 (XML)
  • ClientHandler - 拦截传入的 SOAP 消息
  • 客户端 - 接收响应

通过这种方法,我们可以创建处理程序来记录 SOAP 消息并增加安全性,例如数字签名或加密。

我正在尝试在 Java (v1.17.2) 上使用 gRPC 提供类似的功能。

我的 gRPC 代码基于 this google tutorial,这是一个带有一元方法的简单 hello world。

基于these examples,我写了一个ClientInterceptor

package example.grpc.client;
import java.util.Set;
import io.grpc.*;

public class HelloClientInterceptor implements ClientInterceptor {

@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> methodDescriptor,
        CallOptions callOptions, Channel channel) {
    return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
            channel.newCall(methodDescriptor, callOptions)) {

        @Override
        public void sendMessage(ReqT message) {
            System.out.printf("Sending method '%s' message '%s'%n", methodDescriptor.getFullMethodName(),
                    message.toString());
            super.sendMessage(message);
        }

        @Override
        public void start(Listener<RespT> responseListener, Metadata headers) {
            System.out.println(HelloClientInterceptor.class.getSimpleName());

            ClientCall.Listener<RespT> listener = new ForwardingClientCallListener<RespT>() {
                @Override
                protected Listener<RespT> delegate() {
                    return responseListener;
                }

                @Override
                public void onMessage(RespT message) {
                    System.out.printf("Received message '%s'%n", message.toString());
                    super.onMessage(message);
                }
            };

            super.start(listener, headers);
        }
    };
}

}

我创建了一个ServerInterceptor

package example.grpc.server;

import java.util.Set;

import io.grpc.*;

public class HelloServerInterceptor implements ServerInterceptor {

@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata,
        ServerCallHandler<ReqT, RespT> serverCallHandler) {
    // print class name
    System.out.println(HelloServerInterceptor.class.getSimpleName());

    return Contexts.interceptCall(ctx, serverCall, metadata, serverCallHandler);
}

}

这是(最后)我的问题:

  1. 服务器拦截器如何查看方法执行前后的消息?
  2. 服务器拦截器如何修改消息?
  3. 客户端拦截器如何修改消息?

最终目标是能够编写一个 CipherClientHandler 和一个 CipherServerHandler 来加密线路上的消息字节。我知道 TLS 在实践中是正确的做法,但我希望学生进行自定义实现。

感谢您提供正确方向的任何指示!

【问题讨论】:

    标签: java soap interceptor grpc grpc-java


    【解决方案1】:
    1. 通过“方法执行”,我假设您的意思是前面的“服务器 - 执行方法,响应”。调用服务器方法的确切时间不是拦截 API 的一部分,因此不应依赖。对于今天的异步服务器处理程序,碰巧在调用serverListener.halfClose() 时会调用服务器的方法。但同样,这不应该依赖。目前还不清楚为什么这是必要的。

    2. 服务器拦截器接收请求的ReqT message 和响应的RespT message。要修改消息,只需在调用 super 之前修改这些消息。

    3. 客户端拦截器可以和服务端拦截器做同样的事情;在传递之前修改消息。

    请注意,当我说“修改消息”时,它通常会被实现为“制作带有适当修改的消息的副本”。

    但是,如果您要对消息进行加密/解密,那么从 API 中就不会那么容易了,因为您完全改变了它们的类型。你会得到一个ReqT,你会把它变成字节。为此,您必须修改MethodDescriptors。

    在客户端,这可以在start() 内完成,并将您自己的Marshallers 提供给MethodDescriptor.Builder。您可以访问应用程序的原始MethodDescriptor,因此您可以使用它来序列化为字节。

    Marshaller ENCRYPTING_MARSHALLER = new Marshaller<InputStream>() {
      @Override
      public InputStream parse(InputStream stream) {
        return decrypt(stream);
      }
    
      @Override
      public InputStream stream(InputStream stream) {
        return encrypt(stream);
      }
    };
    
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
            MethodDescriptor<ReqT, RespT> methodDescriptor,
            CallOptions callOptions, Channel channel) {
      ClientCall<InputStream, InputStream> call = channel.newCall(
          methodDescriptor.toBuilder(
            ENCRYPTING_MARSHALLER, ENCRYPTING_MARSHALLER),
          callOptions);
      // Can't use Forwarding* because the generics would break.
      // Note that all of this is basically boilerplate; the marshaller is
      // doing the work.
      return new ClientCall<ReqT, RespT>() {
        @Override
        public void halfClose() {
          call.halfClose();
        }
        // ... ditto for _all_ the other methods on ClientCall
    
        @Override
        public void sendMessage(ReqT message) {
          call.sendMessage(methodDescriptor.streamRequest(message));
        }
    
        @Override
        public void start(Listener<RespT> listener, Metadata headers) {
          call.start(new Listener<InputStream>() {
            @Override
            public void onHalfClose() {
              listener.onHalfClose();
            }
            // ... ditto for _all_ the other methods on Listener
    
            @Override
            public void onMessage(InputStream message) {
              listener.onMessage(methodDescriptor.parseResponse(message));
            }
          }, headers);
        }
      };
    }
    

    服务器端通常类似但更复杂一些,因为您需要重建 ServerServiceDefinition,这无法作为普通拦截器完成。但是碰巧有一个实用程序可以做样板:

    ssd = ServerInterceptors.useMarshalledMessages(ssd, ENCRYPTING_MARSHALLER);
    

    【讨论】:

      猜你喜欢
      • 2017-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-24
      • 2021-04-10
      • 2016-11-13
      • 2020-09-09
      • 1970-01-01
      相关资源
      最近更新 更多