简体   繁体   English

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

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

I am migrating a distributed systems codebase from SOAP (JAX-WS) to gRPC-java. 我正在将分布式系统代码库从SOAP(JAX-WS)迁移到gRPC-java。 We use this codebase to teach remote calls, fault tolerance, security implementations. 我们使用此代码库来教远程调用,容错和安全性实现。

On the JAX-WS architecture, there is an interceptor class (called a SOAP handler) that can intercept SOAP messages. 在JAX-WS体系结构上,有一个拦截器类(称为SOAP处理程序)可以拦截SOAP消息。 You can configure handlers on the client and on the server. 您可以在客户端和服务器上配置处理程序。

For reference, this is a full sequence for a remote call on JAX-WS: 作为参考,这是在JAX-WS上进行远程调用的完整序列:

  • Client - create port (stub) and invoke remote method 客户端-创建端口(存根)并调用远程方法
  • Stub - convert Java objects to SOAP message (XML) 存根-将Java对象转换为SOAP消息(XML)
  • ClientHandler - intercepts outgoing SOAP message and can read/write on it ClientHandler-拦截传出的SOAP消息并可以对其进行读写
  • Network - SOAP request message transmitted 网络-传输SOAP请求消息
  • ServerHandler - intercepts incoming SOAP message, can read/write ServerHandler-拦截传入的SOAP消息,可以读取/写入
  • Tie - convert SOAP message to Java objects 领带-将SOAP消息转换为Java对象
  • Server - execute method, respond 服务器-执行方法,响应
  • ServerHandler - intercepts outgoing SOAP response, can read/write ServerHandler-拦截传出的SOAP响应,可以读取/写入
  • Network - SOAP response message transmitted 网络-传输的SOAP响应消息
  • Client - create port (stub) and invoke remote method 客户端-创建端口(存根)并调用远程方法
  • Stub - convert Java objects to SOAP message (XML) 存根-将Java对象转换为SOAP消息(XML)
  • ClientHandler - intercepts incoming SOAP message ClientHandler-拦截传入的SOAP消息
  • Client - receives response 客户-收到回应

With this approach, we can create handlers to log SOAP messages, and to add security, like digital signature or encryption. 使用这种方法,我们可以创建处理程序来记录SOAP消息,并增加安全性,例如数字签名或加密。

I am trying to have similar capabilities with gRPC on Java (v1.17.2). 我正在尝试在Java(v1.17.2)上具有类似的功能。

I based my gRPC code in this google tutorial , a simple hello world with a unary method. 我在这个Google教程中建立了我的gRPC代码, 是一个使用一元方法的简单问候世界。

Based on these examples , I have written a ClientInterceptor : 基于这些示例 ,我编写了一个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);
        }
    };
}

}

I have created a ServerInterceptor : 我创建了一个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);
}

}

Here are (finally) my questions: 这是(最后)我的问题:

  1. How can the server interceptor see the message before and after the method execution? 服务器拦截器如何在方法执行之前和之后看到消息?
  2. How can the server interceptor modify the message? 服务器拦截器如何修改消息?
  3. How can the client interceptor modify the message? 客户端拦截器如何修改消息?

The end-goal is to be able to write a CipherClientHandler and a CipherServerHandler that would encrypt the message bytes on the wire. 最终目标是能够编写一个CipherClientHandler和一个CipherServerHandler,它们可以加密网络上的消息字节。 I know that TLS is the right way to do it in practice, but I want students to do a custom implementation. 我知道TLS是实践中正确的方法,但我希望学生进行自定义实现。

Thanks for any pointers in the right direction! 感谢您指出正确的方向!

  1. By "method execution", I'm assuming you're meaning "Server - execute method, respond" from earlier. 通过“方法执行”,我假设您的意思是前面的“服务器-执行方法,响应”。 The exact time the server method is called is not part of the interception API and should not be depended on. 调用服务器方法的确切时间不属于拦截API的一部分,因此不应依赖于该时间。 With the async server handlers today, it happens that the server's method is called when serverListener.halfClose() is called. 在当今使用异步服务器处理程序的情况下,碰巧在调用serverListener.halfClose()时调用了服务器的方法。 But again, that should not be depended on. 但是同样,这不应该依赖。 It's unclear why this is necessary. 目前尚不清楚为什么这是必要的。

  2. The server interceptor receives the ReqT message for the request and the RespT message for the response. 服务器拦截器接收ReqT message用于请求和RespT message响应。 To modify the messages, simply modify those messages before calling super . 要修改消息,只需在调用super之前修改这些消息即可。

  3. The client interceptor can do the same as the server interceptor; 客户端拦截器可以与服务器拦截器相同。 modify the message before passing it along. 在传递消息之前先对其进行修改。

Note that when I say "modify the message", it would generally be implemented as "make a copy of the message with the appropriate modifications." 注意,当我说“修改消息”时,通常将其实现为“使用适当的修改来复制消息”。

But if you are going to want to encrypt/decrypt the messages, that doesn't flow as easily from the API because you are completely changing their type. 但是,如果您要加密/解密消息,那么从API中获取消息就不会那么容易了,因为您正在完全更改消息的类型。 You are given a ReqT and you will turn that into bytes. 您将获得一个ReqT ,并将其转换为字节。 To do this you must modify the MethodDescriptor s. 为此,您必须修改MethodDescriptor

On client-side this can be done within start() and providing your own Marshaller s to MethodDescriptor.Builder . 在客户端,这可以在start() ,并向MethodDescriptor.Builder提供您自己的Marshaller You have access to the application's original MethodDescriptor , so you can use that to serialize to bytes. 您可以访问应用程序的原始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);
    }
  };
}

Server-side would normally be similar but a bit more complicated, since you would need to rebuild the ServerServiceDefinition which can't be done as a normal interceptor. 服务器端通常是相似的,但稍微复杂一点,因为您将需要重建ServerServiceDefinition ,而这不能作为普通的拦截器来完成。 But there happens to be a utility that does the boilerplate: 但是碰巧有一个实用工具可以执行样板工作:

ssd = ServerInterceptors.useMarshalledMessages(ssd, ENCRYPTING_MARSHALLER);

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

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