簡體   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