開始食用grpc(之二)
轉載請註明出處:https://www.cnblogs.com/funnyzpc/p/9570992.html
```
前段時間有童鞋找我開專欄、搬家、甚至還有人找我寫書的。。。這其中有大平臺 疼訊、阿里...,也有小平臺 :第八基地、雲聚、西部數碼...,在此再次感謝各位賞識,吾文采拙劣,技術水平較次,實在沒時間寫書,也沒時間給各位解答不熟悉的技術問題...;同時邀請我開專欄、搬家的平臺也請不要重複邀請呢。
額,同時對於轉載的童鞋,需要說明的是:我的部落格是免費公益性質的,若能受到點兒啟發或者有些許的幫助就是對比人最大的肯定,基於此,請各位轉載的童鞋 能 原文轉載(對原作者極大的尊重),若是平臺轉載請在轉載的後的部落格頁面中切勿投放過多的廣告,在此我強調過我的部落格是免費性質的,若是拿來做付費或是賺取廣告費性質的請和我聯絡(可以容許少許的廣告,廣告也不可遮蓋部落格內容),在此請各位諒解哈~,同時感謝深耕在開源一線的童鞋,是你們改善了一線開發人員的處境,也讓整個行業變得快速高效,致敬!
```
此次我就接著上次的話茬把我所瞭解的grpc將完吧,grpc這兩節的內容大致如下:
A->grpc的簡單配置 (上一節)
A>簡單grpc編寫 (上一節)
B>複雜grpc proto服務檔案編寫 (上一節)
C>雙向流式呼叫方法及注意事項 (本節)
D>grpc安全問題及攔截器 (本節)
這次我是這麼安排的,先列舉一個雙向流的編寫過程,然後在講講這裡面的坑,然後再淺談一下grpc安全問題,同時編寫一個簡單的grpc攔截器,若基本配置不是很清楚請仔細閱讀 https://www.cnblogs.com/funnyzpc/p/9501353.html
雙向流式呼叫方法及注意事項:
由於雙向流的使用方式不用於上期所講的,這裡我從編寫一步步講。
先在preview-grpc-lib工程先的proto資料夾下編寫一個包含雙向流的是proto檔案以生成客戶端和伺服器相關程式碼(記得把生成的程式碼放入工程內)。
(MultiStream.proto)
1 syntax = "proto3"; 2 3 option java_multiple_files = true; 4 option java_package = "com.funnyzpc.XXX.grpc.lib.multiStream"; 5 //定義一個服務 6 service MultiStreamService{ 7 rpc queryStream (stream MultiStreamReq) returns (stream MultiStreamResp) { 8 9 } 10 11 } 12 //定義一個請求體(用於傳參) 13 message MultiStreamReq{ 14 int32 page_no=1; 15 int32 page_size=2; 16 MultiStreamDataReq data=3; 17 } 18 19 message MultiStreamDataReq{ 20 string name=1; 21 bool type=2; 22 } 23 //定義一個響應體(用於回參) 24 message MultiStreamResp{ 25 string req_str=1; 26 MultiStreamFirstResp first=2; 27 } 28 message MultiStreamFirstResp{ 29 string f_content=1; 30 int64 idx=2; 31 32 }
這裡可能需要對比著上一節所講的複雜proto檔案編寫的內容,可以看到:請求體和響應體的定義大致都是一樣的,只是在服務定義的時候會有一些些差別>請求體和響應體的前面多了一個關鍵字"stream” ,就是(請求或響應)只要一方是以流的方式傳送就需要宣告為 “stream" 。
編寫個客戶端服務程式碼:
1 @Service 2 public class GrpcMultiStreamClientService { 3 private static final Logger LOG=LoggerFactory.getLogger(GrpcMultiStreamClientService.class); 4 5 @GrpcClient("preview-grpc-server") 6 private Channel rpcChannel; 7 8 /** 9 * grpc>雙向流方式 10 * @return 11 */ 12 public Object queryByStream()throws Exception{ 13 Map<String,Object> resp=new HashMap<>(); 14 15 StreamObserver<MultiStreamResp> req= new StreamObserver<MultiStreamResp>() { 16 @Override 17 public void onNext(MultiStreamResp multiStreamResp) { 18 resp.put("req_str",multiStreamResp.getReqStr()); 19 resp.put("f_content",multiStreamResp.getFirst().getFContent()); 20 resp.put("idx",multiStreamResp.getFirst().getIdx()); 21 LOG.info("onNext()"); 22 //return resp; 23 } 24 25 @Override 26 public void onError(Throwable throwable) { 27 LOG.info("onError()"); 28 } 29 30 @Override 31 public void onCompleted() { 32 LOG.info("onCompleted()"); 33 } 34 }; 35 36 MultiStreamServiceGrpc.MultiStreamServiceStub stud=MultiStreamServiceGrpc.newStub(rpcChannel); 37 StreamObserver<MultiStreamReq> reqStream=stud.queryStream(req); 38 39 MultiStreamDataReq streamDataReq=MultiStreamDataReq.newBuilder() 40 .setName("req>name field") 41 .setType(false) 42 .build(); 43 MultiStreamReq streamReq= MultiStreamReq.newBuilder() 44 .setPageNo(1) 45 .setPageSize(20) 46 .setData(streamDataReq).build(); 47 48 reqStream.onNext(streamReq); 49 reqStream.onCompleted(); 50 return resp; 51 } 52 }
可以在上圖看到,請求方法內首先是要放入一個構造的內部請求方法,請求體也需要放入到StreamObserver這個物件中,這是與之前編寫的grpc客戶端(阻塞)所不一樣的地方,同時構造stub的時候是newStub而不是newBlockingStub ,當然這兩者是有區別的,前者僅適用於http2二進位制流的方式 並且是一個非同步的(這是重點),而後者是僅適用於http1.1的字元明文方式 並且是阻塞方式(這也是重點),後面我會說說這兩者的具體使用區別。
接下來寫一個grpc服務端服務類,這是程式碼:
1 @GrpcService(value= MultiStreamServiceGrpc.class) 2 public class GrpcMultiStreamService extends MultiStreamServiceGrpc.MultiStreamServiceImplBase{ 3 private static final Logger LOG= LoggerFactory.getLogger(GrpcMultiStreamService.class); 4 5 @Override 6 public StreamObserver<MultiStreamReq> queryStream(StreamObserver<MultiStreamResp> resp) { 7 return new StreamObserver<MultiStreamReq>() { 8 @Override 9 public void onNext(MultiStreamReq multiStreamReq) { 10 MultiStreamFirstResp firstResp=MultiStreamFirstResp.newBuilder() 11 .setFContent("f_content") 12 .setIdx(99).build(); 13 MultiStreamResp req=MultiStreamResp.newBuilder() 14 .setReqStr("req_str") 15 .setFirst(firstResp).build(); 16 resp.onNext(req); 17 resp.onCompleted(); 18 } 19 20 @Override 21 public void onError(Throwable throwable) { 22 LOG.info("onError()"); 23 } 24 25 @Override 26 public void onCompleted() { 27 LOG.info("onCompleted()"); 28 } 29 }; 30 31 32 }
整體的構造方法和邏輯程式碼和客戶端程式碼相似,同時服務端的邏輯程式碼基本上全在StreamObserver這個非同步物件中處理,同時這個構造方法也提供了錯誤和完成所對的過載方法,要進行業務處理也必須在過載的onNext方法中編寫。
主題的服務已經編寫完成,現在新增一個控制器來看看這個服務有沒問題。
1 @Autowired 2 private GrpcMultiStreamClientService multiStreamClientService; 3 4 @RequestMapping("/grpc4") 5 public Object grpc4()throws Exception{ 6 return multiStreamClientService.queryByStream(); 7 }
可能你會咦的一聲說:請求是成功的,但為什麼取到的服務端的引數是空呢?
其實這個很好理解,因為客戶端的請求服務方式是流,此種方式下響應當然是非同步的,這裡方便除錯,需要新增執行緒阻塞,才可能獲取到服務端的響應引數(下圖中紅色部分)>
1 @Service 2 public class GrpcMultiStreamClientService { 3 private static final Logger LOG=LoggerFactory.getLogger(GrpcMultiStreamClientService.class); 4 5 @GrpcClient("preview-grpc-server") 6 private Channel rpcChannel; 7 8 /** 9 * grpc>雙向流方式 10 * @return 11 */ 12 public Object queryByStream()throws Exception{ 13 Map<String,Object> resp=new HashMap<>(); 14 15 StreamObserver<MultiStreamResp> req= new StreamObserver<MultiStreamResp>() { 16 @Override 17 public void onNext(MultiStreamResp multiStreamResp) { 18 resp.put("req_str",multiStreamResp.getReqStr()); 19 resp.put("f_content",multiStreamResp.getFirst().getFContent()); 20 resp.put("idx",multiStreamResp.getFirst().getIdx()); 21 LOG.info("onNext()"); 22 //return resp; 23 } 24 25 @Override 26 public void onError(Throwable throwable) { 27 LOG.info("onError()"); 28 } 29 30 @Override 31 public void onCompleted() { 32 LOG.info("onCompleted()"); 33 } 34 }; 35 36 MultiStreamServiceGrpc.MultiStreamServiceStub stud=MultiStreamServiceGrpc.newStub(rpcChannel); 37 StreamObserver<MultiStreamReq> reqStream=stud.queryStream(req); 38 39 MultiStreamDataReq streamDataReq=MultiStreamDataReq.newBuilder() 40 .setName("req>name field") 41 .setType(false) 42 .build(); 43 MultiStreamReq streamReq= MultiStreamReq.newBuilder() 44 .setPageNo(1) 45 .setPageSize(20) 46 .setData(streamDataReq).build(); 47 48 reqStream.onNext(streamReq); 49 reqStream.onCompleted(); 50 Thread.sleep(10000); 51 return resp; 52 } 53 }
可以看到執行緒睡眠了10秒,如果打斷點可以看到 睡眠的過程中會響應客戶端中的onNext方法,再就是把引數放入到resp中,當然客戶端服務為流的方式下一般不做執行緒睡眠的操作,因為伺服器有可能超時,如果超時那可就麻煩了。所以說grpc非同步是有極好的應用場景,比如業務費阻塞,日誌處理等等,同時如果需要直接響應請使用阻塞的方式(上面已經說過了),好了,這個時候,我們看看結果>
ok,可以順利的看到伺服器的響應結果了。
grpc安全問題及攔截器:
對於grpc安全問題,grpc只在服務端提供了 服務端證照驗證 的方式,具體就是在在客戶端請求的時候驗證客戶地址是否是有效而已,預設不使用的時候服務端證照的開關是關閉著的,這個驗證其實也很簡陋,具體的可以看看原始碼便知:
如若開發的系統要保證極高的安全度,建議使用這兩類方式:
A>將客戶端應用和服務端應用放置在同一個內往下,服務端關閉外網直接訪問
B>可以在服務端新增攔截器,使用token的方式來驗證客戶端身份是否合法(這種方式可能需要客戶端設定請求頭)
對於以上兩種安全訪問方式,也可以以混合的方式使用,對於以上後者,我簡單的列舉下如何使用攔截器,就一個簡單的例子呵~
首先填寫一個服務端攔截器>
1 public class GrpcInterceptor implements ServerInterceptor { 2 private static final Logger LOG=LoggerFactory.getLogger(GrpcInterceptor.class); 3 4 @Override 5 public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { 6 LOG.info(call.getAttributes().toString()); 7 String inetSocketString = call.getAttributes() 8 .get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR).toString(); 9 LOG.info(inetSocketString); 10 return next.startCall(call,headers); 11 } 12 }
如上,攔截器實現於grpc 的 ServerInterceptor 來編寫的,如果需要做攔截處理 可以直接在interceptCall方法中編寫相應的邏輯。
然後需要在服務端服務類的註解中宣告所使用的攔截器>
1 @GrpcService(value= MultiStreamServiceGrpc.class,interceptors = GrpcInterceptor.class) 2 public class GrpcMultiStreamService extends MultiStreamServiceGrpc.MultiStreamServiceImplBase{ 3 //此處略 4 }
攔截器宣告可以見以上程式碼紅色部分,以上程式碼的具體邏輯部分與以上GrpcMultiStreamService內容相同,同時順帶說下上面註解中的value變數,這個變數只是宣告當前服務端服務類所使用的grpc的服務類是什麼,當然可以填寫其他的grpc的服務類(一定是proto檔案生成的類才可以),並且不能為空!,同時這裡就不給測試結果囖,讀者打個斷點就知道了。
現在是: 2018-09-01 19:39:12
各位晚安~