崔敏 發表於 2020-4-22 10:29:00

Go gRPC进阶-go-grpc-middleware使用(八)

<h3 id="前言">前言</h3>
<p>上篇介绍了gRPC中TLS认证和自定义方法认证,最后还简单介绍了gRPC拦截器的使用。gRPC自身只能设置一个拦截器,所有逻辑都写一起会比较乱。本篇简单介绍go-grpc-middleware的使用,包括<code>grpc_zap</code>、<code>grpc_auth</code>和<code>grpc_recovery</code>。</p>
<h3 id="go-grpc-middleware简介">go-grpc-middleware简介</h3>
<p>go-grpc-middleware封装了认证(auth), 日志( logging), 消息(message), 验证(validation), 重试(retries) 和监控(retries)等拦截器。</p>
<ul>
<li>安装 <code>go get github.com/grpc-ecosystem/go-grpc-middleware</code></li>
<li>使用</li>
</ul>
<pre><code class="language-go">import "github.com/grpc-ecosystem/go-grpc-middleware"
myServer := grpc.NewServer(
    grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
      grpc_ctxtags.StreamServerInterceptor(),
      grpc_opentracing.StreamServerInterceptor(),
      grpc_prometheus.StreamServerInterceptor,
      grpc_zap.StreamServerInterceptor(zapLogger),
      grpc_auth.StreamServerInterceptor(myAuthFunction),
      grpc_recovery.StreamServerInterceptor(),
    )),
    grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
      grpc_ctxtags.UnaryServerInterceptor(),
      grpc_opentracing.UnaryServerInterceptor(),
      grpc_prometheus.UnaryServerInterceptor,
      grpc_zap.UnaryServerInterceptor(zapLogger),
      grpc_auth.UnaryServerInterceptor(myAuthFunction),
      grpc_recovery.UnaryServerInterceptor(),
    )),
)
</code></pre>
<p><code>grpc.StreamInterceptor</code>中添加流式RPC的拦截器。<br>
<code>grpc.UnaryInterceptor</code>中添加简单RPC的拦截器。</p>
<h3 id="grpc_zap日志记录">grpc_zap日志记录</h3>
<p>1.创建zap.Logger实例</p>
<pre><code class="language-go">func ZapInterceptor() *zap.Logger {
        logger, err := zap.NewDevelopment()
        if err != nil {
                log.Fatalf("failed to initialize zap logger: %v", err)
        }
        grpc_zap.ReplaceGrpcLogger(logger)
        return logger
}
</code></pre>
<p>2.把zap拦截器添加到服务端</p>
<pre><code class="language-go">grpcServer := grpc.NewServer(
        grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
                        grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),
                )),
                grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
                        grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),
                )),
        )
</code></pre>
<p>3.日志分析</p>
<p><img src="https://img2020.cnblogs.com/blog/1508611/202004/1508611-20200421150239484-1399156155.png" alt="" loading="lazy"><br>
各个字段代表的意思如下:</p>
<pre><code class="language-json">{
          "level": "info",                                                // stringzap log levels
          "msg": "finished unary call",                                        // stringlog message

          "grpc.code": "OK",                                                // stringgrpc status code
          "grpc.method": "Ping",                                        / stringmethod name
          "grpc.service": "mwitkow.testproto.TestService",            // stringfull name of the called service
          "grpc.start_time": "2006-01-02T15:04:05Z07:00",               // stringRFC3339 representation of the start time
          "grpc.request.deadline": "2006-01-02T15:04:05Z07:00",         // stringRFC3339 deadline of the current request if supplied
          "grpc.request.value": "something",                                // stringvalue on the request
          "grpc.time_ms": 1.345,                                        // float32 run time of the call in ms

          "peer.address": {
          "IP": "127.0.0.1",                                                // stringIP address of calling party
          "Port": 60216,                                                // int   port call is coming in on
          "Zone": ""                                                        // stringpeer zone for caller
          },
          "span.kind": "server",                                        // stringclient | server
          "system": "grpc",                                                // string

          "custom_field": "custom_value",                                // stringuser defined field
          "custom_tags.int": 1337,                                        // int   user defined tag on the ctx
          "custom_tags.string": "something"                                // stringuser defined tag on the ctx
}
</code></pre>
<p>4.把日志写到文件中</p>
<p>上面日志是在控制台输出的,现在我们把日志写到文件中,修改<code>ZapInterceptor</code>方法。</p>
<pre><code class="language-go">import (
        grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
        "go.uber.org/zap"
        "go.uber.org/zap/zapcore"
        "gopkg.in/natefinch/lumberjack.v2"
)

// ZapInterceptor 返回zap.logger实例(把日志写到文件中)
func ZapInterceptor() *zap.Logger {
        w := zapcore.AddSync(&amp;lumberjack.Logger{
                Filename:"log/debug.log",
                MaxSize:   1024, //MB
                LocalTime: true,
        })

        config := zap.NewProductionEncoderConfig()
        config.EncodeTime = zapcore.ISO8601TimeEncoder
        core := zapcore.NewCore(
                zapcore.NewJSONEncoder(config),
                w,
                zap.NewAtomicLevel(),
        )

        logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
        grpc_zap.ReplaceGrpcLogger(logger)
        return logger
}
</code></pre>
<h3 id="grpc_auth认证">grpc_auth认证</h3>
<p>go-grpc-middleware中的grpc_auth默认使用<code>authorization</code>认证方式,以authorization为头部,包括<code>basic</code>, <code>bearer</code>形式等。下面介绍<code>bearer token</code>认证。<code>bearer</code>允许使用<code>access key</code>(如JSON Web Token (JWT))进行访问。</p>
<p>1.新建grpc_auth服务端拦截器</p>
<pre><code class="language-go">// TokenInfo 用户信息
type TokenInfo struct {
        ID    string
        Roles []string
}

// AuthInterceptor 认证拦截器,对以authorization为头部,形式为`bearer token`的Token进行验证
func AuthInterceptor(ctx context.Context) (context.Context, error) {
        token, err := grpc_auth.AuthFromMD(ctx, "bearer")
        if err != nil {
                return nil, err
        }
        tokenInfo, err := parseToken(token)
        if err != nil {
                return nil, grpc.Errorf(codes.Unauthenticated, " %v", err)
        }
        //使用context.WithValue添加了值后,可以用Value(key)方法获取值
        newCtx := context.WithValue(ctx, tokenInfo.ID, tokenInfo)
        //log.Println(newCtx.Value(tokenInfo.ID))
        return newCtx, nil
}

//解析token,并进行验证
func parseToken(token string) (TokenInfo, error) {
        var tokenInfo TokenInfo
        if token == "grpc.auth.token" {
                tokenInfo.ID = "1"
                tokenInfo.Roles = []string{"admin"}
                return tokenInfo, nil
        }
        return tokenInfo, errors.New("Token无效: bearer " + token)
}

//从token中获取用户唯一标识
func userClaimFromToken(tokenInfo TokenInfo) string {
        return tokenInfo.ID
}
</code></pre>
<p>代码中的对token进行简单验证并返回模拟数据。</p>
<p>2.客户端请求添加<code>bearer token</code></p>
<p>实现和上篇的自定义认证方法大同小异。gRPC 中默认定义了 <code>PerRPCCredentials</code>,是提供用于自定义认证的接口,它的作用是将所需的安全认证信息添加到每个RPC方法的上下文中。其包含 2 个方法:</p>
<ul>
<li><code>GetRequestMetadata</code>:获取当前请求认证所需的元数据</li>
<li><code>RequireTransportSecurity</code>:是否需要基于 TLS 认证进行安全传输</li>
</ul>
<p>接下来我们实现这两个方法</p>
<pre><code class="language-go">// Token token认证
type Token struct {
        Value string
}

const headerAuthorize string = "authorization"

// GetRequestMetadata 获取当前请求认证所需的元数据
func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (mapstring, error) {
        return mapstring{headerAuthorize: t.Value}, nil
}

// RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输
func (t *Token) RequireTransportSecurity() bool {
        return true
}
</code></pre>
<blockquote>
<p>注意:这里要以<code>authorization</code>为头部,和服务端对应。</p>
</blockquote>
<p>发送请求时添加token</p>
<pre><code class="language-go">//从输入的证书文件中为客户端构造TLS凭证
        creds, err := credentials.NewClientTLSFromFile("../tls/server.pem", "go-grpc-example")
        if err != nil {
                log.Fatalf("Failed to create TLS credentials %v", err)
        }
        //构建Token
        token := auth.Token{
                Value: "bearer grpc.auth.token",
        }
        // 连接服务器
        conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&amp;token))
</code></pre>
<blockquote>
<p>注意:Token中的Value的形式要以<code>bearer token值</code>形式。因为我们服务端使用了<code>bearer token</code>验证方式。</p>
</blockquote>
<p>3.把grpc_auth拦截器添加到服务端</p>
<pre><code class="language-go">grpcServer := grpc.NewServer(cred.TLSInterceptor(),
        grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
                grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),
                        grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),
                )),
                grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
                  grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),
                        grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),
                )),
        )
</code></pre>
<p>写到这里,服务端都会拦截请求并进行<code>bearer token</code>验证,使用<code>bearer token</code>是规范了与<code>HTTP</code>请求的对接,毕竟gRPC也可以同时支持<code>HTTP</code>请求。</p>
<h3 id="grpc_recovery恢复">grpc_recovery恢复</h3>
<p>把gRPC中的<code>panic</code>转成<code>error</code>,从而恢复程序。</p>
<p>1.直接把grpc_recovery拦截器添加到服务端</p>
<p>最简单使用方式</p>
<pre><code class="language-go">grpcServer := grpc.NewServer(cred.TLSInterceptor(),
        grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
                grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),
                        grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),
                        grpc_recovery.StreamServerInterceptor,
                )),
                grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
                  grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),
                        grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),
            grpc_recovery.UnaryServerInterceptor(),
                )),
        )
</code></pre>
<p>2.自定义错误返回</p>
<p>当<code>panic</code>时候,自定义错误码并返回。</p>
<pre><code class="language-go">// RecoveryInterceptor panic时返回Unknown错误吗
func RecoveryInterceptor() grpc_recovery.Option {
        return grpc_recovery.WithRecoveryHandler(func(p interface{}) (err error) {
                return grpc.Errorf(codes.Unknown, "panic triggered: %v", p)
        })
}
</code></pre>
<p>添加grpc_recovery拦截器到服务端</p>
<pre><code class="language-go">grpcServer := grpc.NewServer(cred.TLSInterceptor(),
        grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
                grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),
                        grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),
                        grpc_recovery.StreamServerInterceptor(recovery.RecoveryInterceptor()),
                )),
                grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
                  grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),
                        grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),
            grpc_recovery.UnaryServerInterceptor(recovery.RecoveryInterceptor()),
                )),
        )
</code></pre>
<h3 id="总结">总结</h3>
<p>本篇介绍了<code>go-grpc-middleware</code>中的<code>grpc_zap</code>、<code>grpc_auth</code>和<code>grpc_recovery</code>拦截器的使用。<code>go-grpc-middleware</code>中其他拦截器可参考GitHub学习使用。</p>
<p>教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example</p>


</div>
<div id="MySignature" role="contentinfo">
    看完之后若觉得对自己有帮助,恳请点赞或评论。这是对我最大的鼓励!<br><br>
来源:https://www.cnblogs.com/FireworksEasyCool/p/12750339.html
頁: [1]
查看完整版本: Go gRPC进阶-go-grpc-middleware使用(八)