Skip to main content

Metadata & Interceptor

metadata

metadata即元数据,可以提供数据的额外信息,使数据的使用更加高效和智能。

元数据在上下文中有着广泛的应用:

  1. 客户端和服务器之间传递信息,例如认证信息、请求头等。
  2. HTTP请求处理中,可以在上下文中添加元数据,以便在处理请求的各个阶段传递信息。

基本使用

设置

可使用metadata.Newmetadata.Pairs来构建一个metadata

var md1 = metadata.New(map[string]string{
"token": "12345678",
})

var md2 = metadata.Pairs(
"token", "12345678",
)

// map[token:[12345678]]

其返回的类型均为map[string][]string,即一个map,其中键为string类型,值为string类型的切片。

传递时,可通过context携带:

ctx := metadata.NewOutgoingContext(context.Background(), md)
获取

要获取metadata的值,可通过Get方法:

md, _ := metadata.FromIncomingContext(ctx)
clientToken := md.Get("token")[0]

案例

下面以用户认证信息为例:

syntax = "proto3";
option go_package = "./;proto";

import "google/protobuf/empty.proto";

enum Course {
GO = 0;
JAVA = 1;
RUST = 2;
}

message UserInfo {
int64 uid = 1;
string username = 2;
repeated Course courseList = 3;
}

// message UserInfoRequest {
// string token = 1;
// }

message UserInfoResponse {
int32 code = 1;
string msg = 2;
UserInfo data = 3;
}

service User {
// rpc GetUserInfo (UserInfoRequest) returns (UserInfoResponse);
rpc GetUserInfo (google.protobuf.Empty) returns (UserInfoResponse);
}

Interceptor

拦截器(Interceptor)用于在实际的RPC调用前后执行一些通用逻辑,允许在实际RPC方法执行之前或之后插入自定义的逻辑,例如日志记录、认证、限流、错误处理等,类似于中间件,可以在客户端和服务器端实现。

一元拦截器

一元拦截器(Unary Interceptor)是gRPC中的一种拦截器类型,用于拦截一元(Unary)RPC 调用。一元RPC调用是指客户端发送单个请求到服务器,服务器返回单个响应给客户端的调用方式。

基于metadata案例的服务端进行改造:

package main

import (
"advanced-grpc/proto"
"context"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/types/known/emptypb"
)

const TOKEN = "12345678"

type User struct{}

func (user *User) GetUserInfo(ctx context.Context, empty *emptypb.Empty) (*proto.UserInfoResponse, error) {
return &proto.UserInfoResponse{
Code: 1,
Msg: "OK",
Data: &proto.UserInfo{
Uid: 1,
Username: "JASON",
CourseList: []proto.Course{
proto.Course_GO,
proto.Course_JAVA,
proto.Course_RUST,
},
},
}, nil
}

func main() {
// 生成一个拦截器
opt := grpc.UnaryInterceptor(checkToken)

// 注册拦截器
gServer := grpc.NewServer(opt)

proto.RegisterUserServer(gServer, &User{})
listener, _ := net.Listen("tcp", ":8080")
gServer.Serve(listener)
}

// 实现拦截器
func checkToken(
ctx context.Context,
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (resp any, err error) {
// req 请求体
// handler 要请求执行的函数
md, _ := metadata.FromIncomingContext(ctx)
token := md.Get("token")[0]
if token != TOKEN {
return &proto.UserInfoResponse{
Code: 1,
Msg: "Invalid token",
}, nil
}
return handler(ctx, req)
}

但是Go语言的gRPC一元拦截器有个弱点:只能设置一个拦截器,设置之后不能再被设置。此时,可借助一些中间件框架来实现设置多个拦截器,比如grpc_middleware

grpc_middleware

grpc_middleware是一个用于gRPC的中间件框架,提供了一组实用的工具和库来简化在gRPC服务中使用拦截器(Interceptors)。

它支持多个中间件的组合,方便开发者在gRPC请求处理的各个阶段插入自定义逻辑,例如日志记录、认证、限流、监控等。

go get github.com/grpc-ecosystem/go-grpc-middleware

grpc_middleware在配置时,希望返回一个返回:

func main() {
opt := []grpc.ServerOption{
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
// 中间件函数返回一个函数
checkToken(),
Test(),
)),
}

gServer := grpc.NewServer(opt...)
proto.RegisterUserServer(gServer, &User{})
listener, _ := net.Listen("tcp", ":8080")
gServer.Serve(listener)
}

func checkToken() grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler
) (resp any, err error) {

md, _ := metadata.FromIncomingContext(ctx)
token := md.Get("token")[0]

if token != TOKEN {
return &proto.UserInfoResponse{
Code: 1,
Msg: "Invalid token",
}, nil
}
return handler(ctx, req)

}
}

func Test() grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler
) (resp any, err error) {
fmt.Println("Test")
return handler(ctx, req)
}
}