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

时间:2019-01-16 14:12:23

标签: java soap interceptor grpc grpc-java

我正在将分布式系统代码库从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)上的gRPC具有类似的功能。

我的gRPC代码基于this google tutorial,这是一个具有一元方法的简单问候世界。

基于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是实践中正确的方法,但我希望学生进行自定义实现。

感谢任何指向正确方向的指针!

1 个答案:

答案 0 :(得分:0)

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

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

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

请注意,当我说“修改邮件”时,通常将其实现为“对邮件进行适当修改后进行复制。”

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

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