Metadata & Interceptor
metadata
metadata即元数据,可以提供数据的额外信息,使数据的使用更加高效和智能。
元数据在上下文中有着广泛的应用:
- 客户端和服务器之间传递信息,例如认证信息、请求头等。
- HTTP请求处理中,可以在上下文中添加元数据,以便在处理请求的各个阶段传递信息。
基本使用
设置
可使用metadata.New或metadata.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]
案例
下面以用户认证信息为例:
- proto/user.proto
- server/server.go
- client/client.go
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);
}
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) {
md, _ := metadata.FromIncomingContext(ctx)
clientToken := md.Get("token")[0]
if clientToken != TOKEN {
return &proto.UserInfoResponse{
Code: 0,
Msg: "Invalid token",
}, nil
}
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() {
gServer := grpc.NewServer()
proto.RegisterUserServer(gServer, &User{})
listener, _ := net.Listen("tcp", ":8080")
gServer.Serve(listener)
}
package main
import (
"advanced-grpc/proto"
"context"
"encoding/json"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/types/known/emptypb"
)
var md = metadata.Pairs(
"token", "12345678",
)
func main() {
// wrap md with context
ctx := metadata.NewOutgoingContext(context.Background(), md)
conn, _ := grpc.NewClient(":8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
client := proto.NewUserClient(conn)
data, _ := client.GetUserInfo(ctx, &emptypb.Empty{})
jsonData, _ := json.Marshal(data)
fmt.Println(string(jsonData))
}
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)
}
}