콘텐츠로 이동

gRPC 인터셉터

func authInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (any, error) {
md, _ := metadata.FromIncomingContext(ctx)
if len(md["token"]) == 0 {
return nil, status.Error(codes.Unauthenticated, "missing token")
}
return handler(ctx, req)
}
server := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
greeter.RegisterGreeterServer(grpcServer, srv)
})
server.AddUnaryInterceptors(authInterceptor)
server.Start()
func loggingStreamInterceptor(srv any, ss grpc.ServerStream,
info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
logx.Infof("stream started: %s", info.FullMethod)
err := handler(srv, ss)
if err != nil {
logx.Errorf("stream error: %s%v", info.FullMethod, err)
}
return err
}
server.AddStreamInterceptors(loggingStreamInterceptor)

Inject tokens 또는 propagate 헤더 에서 모든 outgoing RPC:

conn, _ := zrpc.NewClient(c.GreeterRpc,
zrpc.WithUnaryClientInterceptor(func(ctx context.Context, method string,
req, reply any, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
ctx = metadata.AppendToOutgoingContext(ctx, "token", getToken())
return invoker(ctx, method, req, reply, cc, opts...)
}),
)
conn, _ := zrpc.NewClient(c.GreeterRpc,
zrpc.WithStreamClientInterceptor(func(ctx context.Context, desc *grpc.StreamDesc,
cc *grpc.ClientConn, method string, streamer grpc.Streamer,
opts ...grpc.CallOption) (grpc.ClientStream, error) {
ctx = metadata.AppendToOutgoingContext(ctx, "request-id", uuid.New().String())
return streamer(ctx, desc, cc, method, opts...)
}),
)

Pass multiple 인터셉터 로 AddUnaryInterceptors; they execute 에서 순서:

server.AddUnaryInterceptors(
authInterceptor, // 1단계: 인증되지 않은 요청을 거부합니다
rateLimitInterceptor, // 2단계: 사용자별 할당량을 적용합니다
loggingInterceptor, // 3단계: 메서드와 지연 시간을 기록합니다
)
func forwardMetadataInterceptor(ctx context.Context, req any,
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
incomingMd, _ := metadata.FromIncomingContext(ctx)
// 핸들러 내부에서 만든 RPC 호출에 특정 헤더를 전달합니다
outgoingCtx := metadata.NewOutgoingContext(ctx, metadata.Join(
incomingMd,
metadata.Pairs("x-request-id", incomingMd.Get("x-request-id")...),
))
return handler(outgoingCtx, req)
}
func loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (any, error) {
start := time.Now()
resp, err := handler(ctx, req)
logx.WithContext(ctx).Infow("rpc call",
logx.Field("method", info.FullMethod),
logx.Field("duration", time.Since(start).String()),
logx.Field("error", err),
)
return resp, err
}

go-zero registers these 자동으로 에서 모든 zrpc.Serverzrpc.Client — 없음 설정 needed:

인터셉터What it does
RecoverInterceptorConverts panics 로 gRPC Internal 오류
PrometheusInterceptorRecords RPC duration과 error-rate 메트릭
TracingInterceptorCreates OpenTelemetry spans, propagates 추적 컨텍스트
BreakerInterceptorOpens 서킷 때 오류 비율 exceeds 임계값
SheddingInterceptorDrops 요청 때 CPU 부하 is critical
TimeoutInterceptorEnforces 별-RPC deadline 에서 서버 설정