繁体   English   中英

如何在客户端和服务器上完全拦截gRPC java一元调用?

[英]How can I fully intercept a gRPC java unary call, on the client, and on the server?

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

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

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

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

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

我正在尝试在Java(v1.17.2)上具有类似的功能。

我在这个Google教程中建立了我的gRPC代码, 是一个使用一元方法的简单问候世界。

基于这些示例 ,我编写了一个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是实践中正确的方法,但我希望学生进行自定义实现。

感谢您指出正确的方向!

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

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

  3. 客户端拦截器可以与服务器拦截器相同。 在传递消息之前先对其进行修改。

注意,当我说“修改消息”时,通常将其实现为“使用适当的修改来复制消息”。

但是,如果您要加密/解密消息,那么从API中获取消息就不会那么容易了,因为您正在完全更改消息的类型。 您将获得一个ReqT ,并将其转换为字节。 为此,您必须修改MethodDescriptor

在客户端,这可以在start() ,并向MethodDescriptor.Builder提供您自己的Marshaller 您可以访问应用程序的原始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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM