gRPC 攔截器能做些什麼?

WindWant發表於2021-07-11

什麼是攔截器?

攔截器是一種橫切維度的功能延展。

具象說明一下,高速收費站就是一種攔截器。它可以做什麼?收費,查證,交通控制等等,面向所有穿行過往的車輛。

gRPC 攔截器主要分為兩種:客戶端攔截器(ClientInterceptor),服務端攔截器(ServerInterceptor),顧名思義,分別於請求的兩端執行相應的前攔截處理。

一、客戶端攔截器

1、作用時機?

請求被分發出去之前。

2、可以做什麼?

a)、請求日誌記錄及監控

b)、新增請求頭資料、以便代理轉發使用

c)、請求或者結果重寫

通常,如果要提供認證資訊的話,可以使用 CallCredentials 實現,雖然,攔截器裡也可以通過設定  CallOptions 來提供。

3、ClientInterceptor 原始碼

@ThreadSafe
public interface ClientInterceptor {

  <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
      MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next);
}

它只有一個方法:interceptCall,對於註冊了相應攔截器的客戶端呼叫,都要經過這個方法,

引數:

1、method:MethodDescriptor 型別,標示請求方法。包括方法全限定名稱、請求服務名稱、請求、結果、序列化工具、冪等等。

2、callOptions:此次請求的附帶資訊。

3、next:執行此次 RPC 請求的抽象連結管道(Channel)

返回結果:

ClientCall,包含請求及結果資訊,並且不為null

4、最簡單的實現

什麼也不做:

public class MyGrpcClientInterceptor implements ClientInterceptor {
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        return next.newCall(method, callOptions);
    }
}

可以看到我們的實現裡,沒有實現任何邏輯,直接執行了 next.newCall 繼續執行客戶端的此次呼叫。

 next.newCall 只能在當前上下文中執行,每次呼叫以及返回都必須是一個完整地迴路,逃逸使用會導致不必要的記憶體洩漏問題。

5、延伸使用

通過 callOption 設定超時及認證資訊

public class MyGrpcClientInterceptor implements ClientInterceptor {
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        return next.newCall(method, callOptions
                .withDeadlineAfter(500, TimeUnit.MILLISECONDS) //設定超時
                .withCallCredentials(new CallCredentials() { //設定認證資訊
                    @Override 
                    public void applyRequestMetadata(RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier) {
                        Metadata metadata = new Metadata();
                        metadata.put(Metadata.Key.of("id", Metadata.ASCII_STRING_MARSHALLER), "king");
                        applier.apply(metadata);
                    }
                    
                    @Override 
                    public void thisUsesUnstableApi() {}
        }));
    }
}

看著是不是很熟悉,stub 呼叫時設定,只不過在這裡換了一個設定場景。

日誌記錄:

public class MyGrpcClientInterceptor implements ClientInterceptor {
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        CallOptions myCallOptions = callOptions
                .withDeadlineAfter(500, TimeUnit.MILLISECONDS) //設定超時
                .withCallCredentials(new CallCredentials() { //設定認證資訊
                    @Override
                    public void applyRequestMetadata(RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier) {
                        Metadata metadata = new Metadata();
                        metadata.put(Metadata.Key.of("id", Metadata.ASCII_STRING_MARSHALLER), "king");
                        applier.apply(metadata);
                    }

                    @Override
                    public void thisUsesUnstableApi() {}
                });
        return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, myCallOptions)) {
            @Override
            public void sendMessage(ReqT message) {
                System.out.println("request method: " + method.getFullMethodName());
                System.out.println("request param:" + message.toString());

                super.sendMessage(message);
            }
        };
    }
}

ForwardingClientCall:ClientCall 的一個抽象實現類,用以請求的代理轉發。為什麼我們這裡要用這個類呢?

其實我們完全可以直接使用 ClientCall 實現,只不過作為頂級抽閒類,我們必須要實現很多方法。而使用 ForwardingClientCall,則我們只需要去重寫我們需要的方法就可以。

如上程式碼:

sendMessage 傳送訊息到請求伺服器,可能會執行多次。此處我們記錄相應的請求引數資訊。

二、服務端攔截器

1、作用時機?

請求被具體的Handler相應前。

2、可以做什麼?

a)訪問認證

b)請求日誌記錄及監控

c)代理轉發

3、ServerInterceptor 原始碼

@ThreadSafe
public interface ServerInterceptor {

  <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
      ServerCall<ReqT, RespT> call,
      Metadata headers,
      ServerCallHandler<ReqT, RespT> next);
}

引數:

call:ServerCall 物件,包含客戶端請求的 MethodDescriptor

headers:請求頭資訊

next:處理鏈條上的下一個處理。

4、最簡單的實現

public class MyGrpcServerInterceptor implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        return next.startCall(call, headers);
    }
}

ServerCallHandler:定義用以實現請求處理的介面類。

5、延伸使用

public class MyGrpcServerInterceptor implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        //提取認證資訊
        String id = headers.get(Metadata.Key.of("id", Metadata.ASCII_STRING_MARSHALLER));
        return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(next.startCall(call, headers)) {
            private long startTime = 0; //處理開始時間
            private ReqT request;
            private boolean valid = false; //認證狀態

            @Override
            public void onComplete() {
                //記錄請求引數及耗時
                System.out.println("process cost: " + (System.nanoTime() - startTime));
                System.out.println("process param: " + request.toString());
                super.onComplete();
            }

            @Override
            public void onMessage(ReqT message) {
                startTime = System.nanoTime();
                request = message;
                if (StringUtils.equals("king", id)) {
                    super.onMessage(message);
                } else {
                    valid = false;
                }
            }

            @Override
            public void onHalfClose() {
                //驗證失敗則返回 Status.UNAUTHENTICATED
                if (!valid) {
                    call.close(Status.UNAUTHENTICATED.withDescription("auth failed"), new Metadata());
                } else {
                    super.onHalfClose();
                }
            }
        };
    }
}

onMessage:接收到請求時進行相應處理,我們這記錄處理開始時間,及請求引數,同時根據提取的認證資訊進行訪問驗證,驗證通過則繼續後續處理,否則設定認證狀態為 false。

onHalfClose:處理認證標示及返回。

onComplete:處理結束記錄請求引數及耗時。

 

相關文章