跟煎鱼学 Go
  • Introduction
  • 第1课 杂谈
    • 1.1 聊一聊,Go 的相对路径问题
    • 1.2 Go 的 fake-useragent 了解一下
    • 1.3 用 Go 来了解一下 Redis 通讯协议
    • 1.4 使用 Gomock 进行单元测试
    • 1.5 在 Go 中恰到好处的内存对齐
    • 1.6 来,控制一下 goroutine 的并发数量
    • 1.7 for-loop 与 json.Unmarshal 性能分析概要
    • 1.8 简单围观一下有趣的 //go: 指令
    • 1.9 我要在栈上。不,你应该在堆上
    • 1.10 defer 会有性能损耗,尽量不要用
    • 1.11 从实践到原理,带你参透 gRPC
    • 1.12 Go1.13 defer 的性能是如何提高的?
    • 1.13 Go 应用内存占用太多,让排查?(VSZ篇)
    • 1.14 干货满满的 Go Modules 和 goproxy.cn
  • 第2课 包管理
    • 2.1 Go依赖管理工具dep
    • 2.2 如此,用dep获取私有库
  • 第3课 gin
    • 3.1 Golang 介绍与环境安装
    • 3.2 Gin搭建Blog API's (一)
    • 3.3 Gin搭建Blog API's (二)
    • 3.4 Gin搭建Blog API's (三)
    • 3.5 使用JWT进行身份校验
    • 3.6 编写一个简单的文件日志
    • 3.7 优雅的重启服务
    • 3.8 为它加上Swagger
    • 3.9 将Golang应用部署到Docker
    • 3.10 定制 GORM Callbacks
    • 3.11 Cron定时任务
    • 3.12 优化配置结构及实现图片上传
    • 3.13 优化你的应用结构和实现Redis缓存
    • 3.14 实现导出、导入 Excel
    • 3.15 生成二维码、合并海报
    • 3.16 在图片上绘制文字
    • 3.17 用Nginx部署Go应用
    • 3.18 Golang交叉编译
    • 3.19 请入门 Makefile
  • 第4课 grpc
    • 4.1 gRPC及相关介绍
    • 4.2 gRPC Client and Server
    • 4.3 gRPC Streaming, Client and Server
    • 4.4 TLS 证书认证
    • 4.5 基于 CA 的 TLS 证书认证
    • 4.6 Unary and Stream interceptor
    • 4.7 让你的服务同时提供 HTTP 接口
    • 4.8 对 RPC 方法做自定义认证
    • 4.9 gRPC Deadlines
    • 4.10 分布式链路追踪
  • 第5课 grpc-gateway
    • 5.1 介绍与环境安装
    • 5.2 Hello World
    • 5.3 Swagger了解一下
    • 5.4 能不能不用证书?
  • 第6课 常用关键字
    • 6.1 panic and recover
    • 6.2 defer
  • 第7课 数据结构
    • 7.1 slice
    • 7.2 slice:最大容量大小是怎么来的
    • 7.3 map:初始化和访问元素
    • 7.4 map:赋值和扩容迁移
    • 7.5 map:为什么遍历 map 是无序的
  • 第8课 标准库
    • 8.1 fmt
    • 8.2 log
    • 8.3 unsafe
  • 第9课 工具
    • 9.1 Go 大杀器之性能剖析 PProf
    • 9.2 Go 大杀器之跟踪剖析 trace
    • 9.3 用 GODEBUG 看调度跟踪
    • 9.4 用 GODEBUG 看GC
  • 第10课 爬虫
    • 9.1 爬取豆瓣电影 Top250
    • 9.2 爬取汽车之家 二手车产品库
    • 9.3 了解一下Golang的市场行情
Powered by GitBook
On this page
  • 前言
  • 有几种方法
  • 看一看
  • grpc.UnaryInterceptor
  • grpc.StreamInterceptor
  • 如何实现多个拦截器
  • gRPC
  • 实现 interceptor
  • Server
  • 验证
  • logging
  • recover
  • 总结
  • 参考
  • 本系列示例代码

Was this helpful?

  1. 第4课 grpc

4.6 Unary and Stream interceptor

Previous4.5 基于 CA 的 TLS 证书认证Next4.7 让你的服务同时提供 HTTP 接口

Last updated 5 years ago

Was this helpful?

项目地址:

前言

我想在每个 RPC 方法的前或后做某些事情,怎么做?

本章节将要介绍的拦截器(interceptor),就能帮你在合适的地方实现这些功能。

有几种方法

在 gRPC 中,大类可分为两种 RPC 方法,与拦截器的对应关系是:

  • 普通方法:一元拦截器(grpc.UnaryInterceptor)

  • 流方法:流拦截器(grpc.StreamInterceptor)

看一看

grpc.UnaryInterceptor

func UnaryInterceptor(i UnaryServerInterceptor) ServerOption {
    return func(o *options) {
        if o.unaryInt != nil {
            panic("The unary server interceptor was already set and may not be reset.")
        }
        o.unaryInt = i
    }
}

函数原型:

type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)

通过查看源码可得知,要完成一个拦截器需要实现 UnaryServerInterceptor 方法。形参如下:

  • ctx context.Context:请求上下文

  • req interface{}:RPC 方法的请求参数

  • info *UnaryServerInfo:RPC 方法的所有信息

  • handler UnaryHandler:RPC 方法本身

grpc.StreamInterceptor

func StreamInterceptor(i StreamServerInterceptor) ServerOption

函数原型:

type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error

StreamServerInterceptor 与 UnaryServerInterceptor 形参的意义是一样,不再赘述

如何实现多个拦截器

另外,可以发现 gRPC 本身居然只能设置一个拦截器,难道所有的逻辑都只能写在一起?

import "github.com/grpc-ecosystem/go-grpc-middleware"

myServer := grpc.NewServer(
    grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
        ...
    )),
    grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
       ...
    )),
)

gRPC

从本节开始编写 gRPC interceptor 的代码,我们会将实现以下拦截器:

  • logging:RPC 方法的入参出参的日志输出

  • recover:RPC 方法的异常保护和日志输出

实现 interceptor

logging

func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    log.Printf("gRPC method: %s, %v", info.FullMethod, req)
    resp, err := handler(ctx, req)
    log.Printf("gRPC method: %s, %v", info.FullMethod, resp)
    return resp, err
}

recover

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", e)
        }
    }()

    return handler(ctx, req)
}

Server

import (
    "context"
    "crypto/tls"
    "crypto/x509"
    "errors"
    "io/ioutil"
    "log"
    "net"
    "runtime/debug"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/status"
    "google.golang.org/grpc/codes"
    "github.com/grpc-ecosystem/go-grpc-middleware"

    pb "github.com/EDDYCJY/go-grpc-example/proto"
)

...

func main() {
    c, err := GetTLSCredentialsByCA()
    if err != nil {
        log.Fatalf("GetTLSCredentialsByCA err: %v", err)
    }

    opts := []grpc.ServerOption{
        grpc.Creds(c),
        grpc_middleware.WithUnaryServerChain(
            RecoveryInterceptor,
            LoggingInterceptor,
        ),
    }

    server := grpc.NewServer(opts...)
    pb.RegisterSearchServiceServer(server, &SearchService{})

    lis, err := net.Listen("tcp", ":"+PORT)
    if err != nil {
        log.Fatalf("net.Listen err: %v", err)
    }

    server.Serve(lis)
}

验证

logging

启动 simple_server/server.go,执行 simple_client/client.go 发起请求,得到结果:

$ go run server.go
2018/10/02 13:46:35 gRPC method: /proto.SearchService/Search, request:"gRPC" 
2018/10/02 13:46:35 gRPC method: /proto.SearchService/Search, response:"gRPC Server"

recover

在 RPC 方法中人为地制造运行时错误,再重复启动 server/client.go,得到结果:

client

$ go run client.go
2018/10/02 13:19:03 client.Search err: rpc error: code = Internal desc = Panic err: assignment to entry in nil map
exit status 1

server

$ go run server.go
goroutine 23 [running]:
runtime/debug.Stack(0xc420223588, 0x1033da9, 0xc420001980)
    /usr/local/Cellar/go/1.10.1/libexec/src/runtime/debug/stack.go:24 +0xa7
runtime/debug.PrintStack()
    /usr/local/Cellar/go/1.10.1/libexec/src/runtime/debug/stack.go:16 +0x22
main.RecoveryInterceptor.func1(0xc420223a10)
...

检查服务是否仍然运行,即可知道 Recovery 是否成功生效

总结

通过本章节,你可以学会最常见的拦截器使用方法。接下来其它“新”需求只要举一反三即可。

参考

本系列示例代码

关于这一点,你可以放心。采用开源项目 就可以解决这个问题,本章也会使用它。

https://github.com/EDDYCJY/go-grpc-example
go-grpc-middleware
go-grpc-example