【GO】30.grpc拦截器源码分析

news2024/9/30 7:30:02

一.服务端拦截器server端原理

  1. serverOptions配置中的Interceptor,其中unary为一元拦截器,stream为流式拦截器。

本文只看一元式拦截器,即最常见的客户端向服务器发送单个请求并返回单个响应。

  1. 创建一个新的grpc server时,这个方法将拦截器进行了组合封装

  1. chainUnaryServerInterceptors具体代码,现将unaryInt和数组chainUnaryInts进行了合并,拦截器数量小于等于1的逻辑很好理解,重点看一下len>1的情况,chainUnaryInterceptors这个函数将拦截器数据递归封装为一个总的拦截器,这个总的拦截器包含数组中所有拦截器的逻辑。

  1. 下面是封装过程,chainUnaryInterceptors函数输入为拦截器数组,输出为封装好的总拦截器。具体逻辑返回新封装的拦截器,新封装的拦截器的结果为执行数组中第一个拦截器的结果。第一个拦截器的结果实际上是执行handler返回的。而这里的handler又要通过getChainUnaryHandler来获取。getChainUnaryHandler获取handler为一个递归过程,根据curr的位置调用下一个拦截器,直到执行最后一个拦截器的handler即finalHandler,这个finalHandler就是是我们grpc的具体方法。这样形成一个函数的嵌套,保障执行完所有的拦截器的逻辑,最后调用grpc的具体方法。

  1. 以上只是组装了拦截器,我们看具体调用逻辑

Serve里代码太长,直接看最重要的,监听tcp返回一个连接,然后开个gorutine处理客户端请求

创建http2 transport后再次开启goruotine处理流

一路跟下来,根据客户端的method匹配的服务注册的具体grpc方法,并执行。具体执行的代码写在创建的pb文件中。

  1. 我的proto文件

syntax = "proto3";

package protocol_demo;

option go_package = "lib/proto/protocol_demo";

message HelloWorldReq {
    int64 Id = 1;
}

message HelloWorldRsp {
    string Message = 2;
}

service HelloWorld {
    rpc GetHelloWorld (HelloWorldReq) returns (HelloWorldRsp) {
    }
}

生成的bp文件,注册服务的时候将方法名和对应的方法添加到Methods中,本例中对应的方法为_HelloWorld_GetHelloWorld_Handler。上面的md.Handler就是执行的这个方法,看到如果没有拦截器直接执行具体方法。如果有拦截器,则执行拦截器即:return interceptor

这个intercepor就是上文中封装的总拦截器。

以上就是grpc server端拦截器实现的源码逻辑。

二. 实现一个拦截器

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
    "gopractice/lib/proto/protocol_demo"
    "net"
    "runtime/debug"
    "time"
)

func main() {
    lis, err := net.Listen("tcp", ":50052")
    if err != nil {
        fmt.Printf("failed to listen: %s \n", err)
        return
    }

    opts := []grpc.ServerOption{
        grpc.ChainUnaryInterceptor(RecoveryInterceptor, LoggerInterceptor),
    }

    s := grpc.NewServer(opts...)
    protocol_demo.RegisterHelloWorldServer(s, &server{})
    fmt.Println("success")
    err = s.Serve(lis)

    if err != nil {
        fmt.Printf("failed to start grpc server: %s \n", err)
        return
    }
}

type server struct {
}

func (s *server) GetHelloWorld(ctx context.Context, req *protocol_demo.HelloWorldReq) (rsp *protocol_demo.HelloWorldRsp, err error) {
    rsp = &protocol_demo.HelloWorldRsp{}
    rsp.Message = fmt.Sprintf("hello user: %d", req.Id)
    fmt.Printf("hello method\n")
    return rsp, nil
}

func LoggerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
    t := time.Now()
    fmt.Printf("gRpc begin method: method: %s | req: %v | time: %s", info.FullMethod, req, t.Format("2006-01-02 15:04:05.000000"))
    fmt.Println()
    resp, err = handler(ctx, req)
    fmt.Printf("gRpc finish method: %s | rsp: %v | time: %s | durations: %s", info.FullMethod, resp, req, time.Since(t))
    fmt.Println()
    return
}

func RecoveryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
    defer func() {
        if e := recover(); e != nil {
            debug.PrintStack()
            err = status.Errorf(codes.Internal, "Panic err: %v | %s", e, string(debug.Stack()))
            fmt.Println()
        }
    }()
    fmt.Printf("RecoveryInterceptor in\n")
    resp, err = handler(ctx, req)
    fmt.Printf("RecoveryInterceptor out\n")
    return
}

三.客户端拦截器

客户端与服务端类似,直接贴相关代码

组装拦截器的逻辑是一样的,只不过handler变为了invoker

执行调用是有拦截器执行拦截器逻辑,没有直接invoke

创建一个streaming RPC直接发送接收相关数据完成调用

四. 客户端代码实现

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/connectivity"
    "google.golang.org/grpc/credentials/insecure"
    "google.golang.org/grpc/status"
    "gopractice/lib/proto/protocol_demo"
    "runtime/debug"
    "time"
)

func main() {
    conn, err := grpc.Dial("localhost:50052", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithChainUnaryInterceptor(LoggerInterceptor, RecoveryInterceptor))

    if err != nil {
        fmt.Printf("new grpc client failed: %s \n", err)
        return
    }

    defer conn.Close()

    c := protocol_demo.NewHelloWorldClient(conn)

    r, err := c.GetHelloWorld(context.Background(), &protocol_demo.HelloWorldReq{Id: 100})

    if err != nil {
        fmt.Printf("request GetHelloWorld faild: %s \n", err)
        return
    }

    fmt.Printf("success, message is : %s \n", r.Message)

}


func LoggerInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    t := time.Now()
    fmt.Printf("gRpc begin method: method: %s | req: %v | time: %s", method, req, t.Format("2006-01-02 15:04:05.000000"))
    fmt.Println()
    err := invoker(ctx, method, req, reply, cc, opts...)
    fmt.Printf("gRpc finish method: %s | rsp: %v | time: %s | durations: %s", method, reply, t.Format("2006-01-02 15:04:05.000000"), time.Since(t))
    fmt.Println()
    return err
}

func RecoveryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) {
    defer func() {
        if e := recover(); e != nil {
            debug.PrintStack()
            err = status.Errorf(codes.Internal, "Panic err: %v | %s", e, string(debug.Stack()))
            fmt.Println()
        }
    }()
    fmt.Printf("RecoveryInterceptor in\n")
    err = invoker(ctx, method, req, reply, cc, opts...)
    fmt.Printf("RecoveryInterceptor out\n")
    return
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/375088.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么?你还不明白什么是ClassLoader?不如试试从JVM来入手ClassLoader是什么玩意吧!

文章目录环境配置篇如何执行一个文件配置JDK环境(简述)Java文件执行流程编译加载JVM环境准备BootStrapClassLoadersun.misc.laucherAppClassLoader解释执行回收ClassLoader讲解主要的三个ClassLoader双亲委派模型loadClass方法讲解自定义ClassLoaderJVM内…

多芯片设计 Designing For Multiple Die

Why a system-level approach is essential, and why its so challenging作者:Ann MutschlerAnn Mutschler is executive editor at Semiconductor Engineering.将多个裸片或芯粒集成到一个封装中,与将它们放在同一硅片上有着很大的区别。在同一硅片上&a…

断点续传实现

断点续传 1、 什么是断点续传 通常视频文件都比较大,所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制,但是客户的网络环境质量、电脑硬件环境等参差不齐,如果一个大文件快上传完了网断了没有上…

分布式之ZAB协议

写在前面 假定我们现在使用zk执行了如下的指令: [zk: 192.168.0.10:2181(CONNECTED) 0] create /dongshidaddy 123 Created /dongshidaddy [zk: 192.168.0.10:2181(CONNECTED) 1] create /dongshidaddy/mongo 456 Created /dongshidaddy/mongo假定因为节点故障最终…

Python曲线肘部点检测-膝部点自动检测

文章目录一. 术语解释二. 拐点检测肘部法则是经常使用的法则。很多时候,可以凭人工经验去找最优拐点,但有时需要自动寻找拐点。最近解决了一下这个问题,希望对各位有用。一. 术语解释 **肘形曲线(elbow curve)**类似人胳膊状的曲线&#xff…

Echarts 每个柱子一种渐变色的象形柱状图

第023个点击查看专栏目录本示例是解决每个柱状图的每一个柱子都呈现一种渐变色,每个柱子的颜色都不同。这里同时采用了象形的柱状图效果。 文章目录示例效果示例源代码(共125行)相关资料参考专栏介绍示例效果 示例源代码(共125行&…

JavaScript DOM【快速掌握知识点】

目录 DOM简介 获取元素 修改元素 添加和移除元素 事件处理 DOM简介 JavaScript DOM 是指 JavaScript 中的文档对象模型(Document Object Model);它允许 JavaScript 与 HTML 页面交互,使开发者可以通过编程方式动态地修改网页…

RocketMQ源码分析

RocketMQ源码深入剖析 1 RocketMQ介绍 RocketMQ 是阿里巴巴集团基于高可用分布式集群技术,自主研发的云正式商用的专业消息中间件,既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠…

汽轮机胀差及轴向位移(转载的)

这个文章是微信公众号推送看到的。搬运到这里方便以后学习用。 1、轴向位移和胀差的概念 轴位移指的是轴的位移量,而胀差则指的是轴相对于汽缸的相对膨胀量,一般轴向位移变化时其数值较小。轴向位移为正值时,大轴向发电机方向移,若此时汽缸膨…

如何快速了解一个系统?

前言 开发人员经常会面临下面一些场景: 新人入职,需要学习已有系统,作为 landing 的一部分,如何学习?被拉过去参与一个陌生系统的迭代开发或者系统维护(bugfix),如何快速上手&…

关键词聚类和凸现分析-实战1——亚急性甲状腺炎的

审稿人问题第8页第26行-请指出#是什么意思,并解释为什么亚急性甲状腺炎在这里被列为#8。我认为在搜索亚急性甲状腺炎相关文章时,关键词共现分析应该提供关键词共现的数据。这些结果的实际用途是什么?亚急性甲状腺炎是一种较为罕见但重要的甲状腺疾病&am…

vue + qiankun 项目搭建

一、cli3构建vue2项目 1、前期工作:查看cli安装情况与安装 npm install -g vue/cli 已安装情况查看:vue -V(大写的V) 2、新建项目 vue create main-project 3、选择自定义配置 配置选择 选择vue版本、babel、router、vuex、css预处理器、lint格式校…

【神经网络】GRU

1.什么是GRU GRU(Gate Recurrent Unit)门控循环单元,是循环神经网络(RNN)的变种种,与LSTM类似通过门控单元解决RNN中不能长期记忆和反向传播中的梯度等问题。与LSTM相比,GRU内部的网络架构较为简…

Android 实现菜单拖拽排序

效果图简介本文主角是ItemTouchHelper。它是RecyclerView对于item交互处理的一个「辅助类」,主要用于拖拽以及滑动处理。以接口实现的方式,达到配置简单、逻辑解耦、职责分明的效果,并且支持所有的布局方式。功能拆解功能实现4.1、实现接口自…

【员工管理系统】

员工管理系统前言需求分析系统设计系统框图所需技术系统实现编写代码测试前言 这是一个使用epoll实现TCP并发服务器,并让客户端登录服务器可以进行员工的管理,员工的信息存储在sqlite数据库中,对数据库进行增删改查实现对员工的添加&#xf…

一文理解服务端渲染SSR的原理,附实战基于vite和webpack打造React和Vue的SSR开发环境

SSR和CSR 首先,我们先要了解什么是SSR和CSR,SSR是服务端渲染,CSR是客户端渲染,服务端渲染是指 HTTP 服务器直接根据用户的请求,获取数据,生成完整的 HTML 页面返回给客户端(浏览器)展…

嵌入式 STM32 通讯协议--MODBUS

目录 一、自定义通信协议 1、协议介绍 2、网络协议 3、自定义的通信协议 二、MODBUS通信协议 1、概述 2、MODBUS帧结构 协议描述 3、MODBUS数据模型 4、MODBUS事务处理的定义 5、MODBUS功能码 6、功能码定义 7、MODBUS数据链路层 8、MODBUS地址规则 9、MO…

SpringBoot 2.x ——使用 mail 实现邮件发送

文章目录前言环境、版本等pom依赖引入springboot项目配置文件获取邮箱授权码配置properties文件定义接口信息接收类编写邮件发送服务类编写接口swagger测试1、简单邮件发送2、html格式发送(支持附件)前言 最近再看xxl-job的源码,其中在邮件告警通知中使用到了告警信…

Go调用dll 解决方案 dll查看工具

准备工作 Go需要1.10版本,即支持动态链接库 基本调用代码 lib : syscall.NewLazyDLL("lib/plugin.dll") // 读取dll f : lib.NewProc("Sum") // 调用dll函数 res, _, _ : f.Call(param) // 传值 fmt.Println(res)可能出现的问题 %1 is not a …

移动硬盘不显示怎么办?恢复硬盘的方法汇总

在日常工作和生活中,移动硬盘是非常重要的存储设备,它们可以储存大量的数据,比如照片、音乐、视频、文档等。但是,有时候你可能会遇到移动硬盘不显示的问题。这个问题通常会让人感到困惑,因为你无法访问移动硬盘里的数…