```
前段时间有童鞋找我开专栏, 搬家, 甚至还有人找我写书的... 这其中有大平台 疼讯, 阿里..., 也有小平台 : 第八基地, 云聚, 西部数码..., 在此再次感谢各位赏识, 吾文采拙劣, 技术水平较次, 实在没时间写书, 也没时间给各位解答不熟悉的技术问题...; 同时邀请我开专栏, 搬家的平台也请不要重复邀请呢.
额, 同时对于转载的童鞋, 需要说明的是: 我的博客是免费公益性质的, 若能受到点儿启发或者有些许的帮助就是对比人最大的肯定, 基于此, 请各位转载的童鞋 能 原文转载(对原作者极大的尊重), 若是平台转载请在转载的后的博客页面中切勿投放过多的广告, 在此我强调过我的博客是免费性质的, 若是拿来做付费或是赚取广告费性质的请和我联系(可以容许少许的广告, 广告也不可遮盖博客内容), 在此请各位谅解哈~, 同时感谢深耕在开源一线的童鞋, 是你们改善了一线开发人员的处境, 也让整个行业变得快速高效, 致敬!
```
此次我就接着上次的话茬把我所了解的 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)
- syntax = "proto3";
- option java_multiple_files = true;
- option java_package = "com.funnyzpc.XXX.grpc.lib.multiStream";
- // 定义一个服务
- service MultiStreamService{
- rpc queryStream (stream MultiStreamReq) returns (stream MultiStreamResp) {
- }
- }
- // 定义一个请求体(用于传参)
- message MultiStreamReq{
- int32 page_no=1;
- int32 page_size=2;
- MultiStreamDataReq data=3;
- }
- message MultiStreamDataReq{
- string name=1;
- bool type=2;
- }
- // 定义一个响应体(用于回参)
- message MultiStreamResp{
- string req_str=1;
- MultiStreamFirstResp first=2;
- }
- message MultiStreamFirstResp{
- string f_content=1;
- int64 idx=2;
- }
这里可能需要对比着上一节所讲的复杂 proto 文件编写的内容, 可以看到: 请求体和响应体的定义大致都是一样的, 只是在服务定义的时候会有一些些差别>请求体和响应体的前面多了一个关键字 "stream" , 就是 (请求或响应) 只要一方是以流的方式发送就需要声明为 "stream" .
编写个客户端服务代码:
- @Service
- public class GrpcMultiStreamClientService {
- private static final Logger LOG=LoggerFactory.getLogger(GrpcMultiStreamClientService.class);
- @GrpcClient("preview-grpc-server")
- private Channel rpcChannel;
- /**
- * grpc > 双向流方式
- * @return
- */
- public Object queryByStream()throws Exception{
- Map<String,Object> resp=new HashMap<>();
- StreamObserver<MultiStreamResp> req= new StreamObserver<MultiStreamResp>() {
- @Override
- public void onNext(MultiStreamResp multiStreamResp) {
- resp.put("req_str",multiStreamResp.getReqStr());
- resp.put("f_content",multiStreamResp.getFirst().getFContent());
- resp.put("idx",multiStreamResp.getFirst().getIdx());
- LOG.info("onNext()");
- //return resp;
- }
- @Override
- public void onError(Throwable throwable) {
- LOG.info("onError()");
- }
- @Override
- public void onCompleted() {
- LOG.info("onCompleted()");
- }
- };
- MultiStreamServiceGrpc.MultiStreamServiceStub stud=MultiStreamServiceGrpc.newStub(rpcChannel);
- StreamObserver<MultiStreamReq> reqStream=stud.queryStream(req);
- MultiStreamDataReq streamDataReq=MultiStreamDataReq.newBuilder()
- .setName("req>name field")
- .setType(false)
- .build();
- MultiStreamReq streamReq= MultiStreamReq.newBuilder()
- .setPageNo(1)
- .setPageSize(20)
- .setData(streamDataReq).build();
- reqStream.onNext(streamReq);
- reqStream.onCompleted();
- return resp;
- }
- }
可以在上图看到, 请求方法内首先是要放入一个构造的内部请求方法, 请求体也需要放入到 StreamObserver 这个对象中, 这是与之前编写的 grpc 客户端 (阻塞) 所不一样的地方, 同时构造 stub 的时候是 newStub 而不是 newBlockingStub , 当然这两者是有区别的, 前者仅适用于 http2 二进制流的方式 并且是一个异步的(这是重点), 而后者是仅适用于 http1.1 的字符明文方式 并且是阻塞方式(这也是重点), 后面我会说说这两者的具体使用区别.
接下来写一个 grpc 服务端服务类, 这是代码:
- @GrpcService(value= MultiStreamServiceGrpc.class)
- public class GrpcMultiStreamService extends MultiStreamServiceGrpc.MultiStreamServiceImplBase{
- private static final Logger LOG= LoggerFactory.getLogger(GrpcMultiStreamService.class);
- @Override
- public StreamObserver<MultiStreamReq> queryStream(StreamObserver<MultiStreamResp> resp) {
- return new StreamObserver<MultiStreamReq>() {
- @Override
- public void onNext(MultiStreamReq multiStreamReq) {
- MultiStreamFirstResp firstResp=MultiStreamFirstResp.newBuilder()
- .setFContent("f_content")
- .setIdx(99).build();
- MultiStreamResp req=MultiStreamResp.newBuilder()
- .setReqStr("req_str")
- .setFirst(firstResp).build();
- resp.onNext(req);
- resp.onCompleted();
- }
- @Override
- public void onError(Throwable throwable) {
- LOG.info("onError()");
- }
- @Override
- public void onCompleted() {
- LOG.info("onCompleted()");
- }
- };
- 31 32 }
整体的构造方法和逻辑代码和客户端代码相似, 同时服务端的逻辑代码基本上全在 StreamObserver 这个异步对象中处理, 同时这个构造方法也提供了错误和完成所对的重载方法, 要进行业务处理也必须在重载的 onNext 方法中编写.
主题的服务已经编写完成, 现在添加一个控制器来看看这个服务有没问题.
- @Autowired
- private GrpcMultiStreamClientService multiStreamClientService;
- @RequestMapping("/grpc4")
- public Object grpc4()throws Exception{
- return multiStreamClientService.queryByStream();
- }
可能你会咦的一声说: 请求是成功的, 但为什么取到的服务端的参数是空呢?
其实这个很好理解, 因为客户端的请求服务方式是流, 此种方式下响应当然是异步的, 这里方便调试, 需要添加线程阻塞, 才可能获取到服务端的响应参数(下图中红色部分)>
- @Service
- public class GrpcMultiStreamClientService {
- private static final Logger LOG=LoggerFactory.getLogger(GrpcMultiStreamClientService.class);
- @GrpcClient("preview-grpc-server")
- private Channel rpcChannel;
- /**
- * grpc > 双向流方式
- * @return
- */
- public Object queryByStream()throws Exception{
- Map<String,Object> resp=new HashMap<>();
- StreamObserver<MultiStreamResp> req= new StreamObserver<MultiStreamResp>() {
- @Override
- public void onNext(MultiStreamResp multiStreamResp) {
- resp.put("req_str",multiStreamResp.getReqStr());
- resp.put("f_content",multiStreamResp.getFirst().getFContent());
- resp.put("idx",multiStreamResp.getFirst().getIdx());
- LOG.info("onNext()");
- //return resp;
- }
- @Override
- public void onError(Throwable throwable) {
- LOG.info("onError()");
- }
- @Override
- public void onCompleted() {
- LOG.info("onCompleted()");
- }
- };
- MultiStreamServiceGrpc.MultiStreamServiceStub stud=MultiStreamServiceGrpc.newStub(rpcChannel);
- StreamObserver<MultiStreamReq> reqStream=stud.queryStream(req);
- MultiStreamDataReq streamDataReq=MultiStreamDataReq.newBuilder()
- .setName("req>name field")
- .setType(false)
- .build();
- MultiStreamReq streamReq= MultiStreamReq.newBuilder()
- .setPageNo(1)
- .setPageSize(20)
- .setData(streamDataReq).build();
- reqStream.onNext(streamReq);
- reqStream.onCompleted();
- Thread.sleep(10000);
- return resp;
- }
- }
可以看到线程睡眠了 10 秒, 如果打断点可以看到 睡眠的过程中会响应客户端中的 onNext 方法, 再就是把参数放入到 resp 中, 当然客户端服务为流的方式下一般不做线程睡眠的操作, 因为服务器有可能超时, 如果超时那可就麻烦了. 所以说 grpc 异步是有极好的应用场景, 比如业务费阻塞, 日志处理等等, 同时如果需要直接响应请使用阻塞的方式(上面已经说过了), 好了, 这个时候, 我们看看结果>
ok, 可以顺利的看到服务器的响应结果了.
grpc 安全问题及拦截器:
对于 grpc 安全问题, grpc 只在服务端提供了 服务端证书验证 的方式, 具体就是在在客户端请求的时候验证客户地址是否是有效而已, 默认不使用的时候服务端证书的开关是关闭着的, 这个验证其实也很简陋, 具体的可以看看源码便知:
如若开发的系统要保证极高的安全度, 建议使用这两类方式:
A > 将客户端应用和服务端应用放置在同一个内往下, 服务端关闭外网直接访问
B > 可以在服务端添加拦截器, 使用 token 的方式来验证客户端身份是否合法(这种方式可能需要客户端设置请求头)
对于以上两种安全访问方式, 也可以以混合的方式使用, 对于以上后者, 我简单的列举下如何使用拦截器, 就一个简单的例子呵~
首先填写一个服务端拦截器>
- public class GrpcInterceptor implements ServerInterceptor {
- private static final Logger LOG=LoggerFactory.getLogger(GrpcInterceptor.class);
- @Override
- public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
- LOG.info(call.getAttributes().toString());
- String inetSocketString = call.getAttributes()
- .get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR).toString();
- LOG.info(inetSocketString);
- return next.startCall(call,headers);
- }
- }
如上, 拦截器实现于 grpc 的 ServerInterceptor 来编写的, 如果需要做拦截处理 可以直接在 interceptCall 方法中编写相应的逻辑.
然后需要在服务端服务类的注解中声明所使用的拦截器>
- @GrpcService(value= MultiStreamServiceGrpc.class,interceptors = GrpcInterceptor.class)
- public class GrpcMultiStreamService extends MultiStreamServiceGrpc.MultiStreamServiceImplBase{
- // 此处略
- }
拦截器声明可以见以上代码红色部分, 以上代码的具体逻辑部分与以上 GrpcMultiStreamService 内容相同, 同时顺带说下上面注解中的 value 变量, 这个变量只是声明当前服务端服务类所使用的 grpc 的服务类是什么, 当然可以填写其他的 grpc 的服务类(一定是 proto 文件生成的类才可以), 并且不能为空!, 同时这里就不给测试结果囖, 读者打个断点就知道了.
来源: https://www.cnblogs.com/funnyzpc/p/9570992.html