什麼是攔截器?
攔截器是一種橫切維度的功能延展。
具象說明一下,高速收費站就是一種攔截器。它可以做什麼?收費,查證,交通控制等等,面向所有穿行過往的車輛。
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:處理結束記錄請求引數及耗時。