An interceptor is a piece of logic that wraps an RPC call — exactly like an HTTP middleware.
{Pain Point}: 多数新人在调试时发现日志没有打印。却找不到原因——其实是因为把 `recovery` 写在了最内层,导致 panic 被提前吞掉。<\/div>
拦截顺序
-
先注册 → 最外层
-
后注册 → 更靠近业务 Handler
go
// 正确顺序示例
srv.Use(
recovery.UnaryInterceptor,// 捕获所有 panic
requestid.UnaryServerInterceptor,// 注入 RequestID
logging.UnaryInterceptor,// 打印日志
authn.UnaryInterceptor。// 鉴权
)
两种注册方式
| 方法 |
示例 |
场景 |
| Use |
srv.Use |
动态添加,可随时增删 |
| WithMiddleware |
grpcServer.NewServer) |
在创建时一次性声明 |
内置拦截器详解
下面列出 Go‑Wind 提供的 13+ 开箱即用的 interceptor。我们主要解释最常用的六个。
| 名称 | 作用 | 推荐位置
|
<
| recovery | 捕获 panic 并返回 codes.Internal | 最外层
|
| logging | 记录方法名、状态码、耗时 | 靠近业务,但在 authn 前可记录鉴权失败
|
| requestid | 自动生成/传播唯一请求 ID | 最外层或紧跟 recovery
|
| timeout | 为未设 deadline 的调用设置默认超时 | 业务前
|
| ratelimit | 令牌桶或漏桶限流 | 业务前。建议放在 logging 后
|
| authn | JWT / token 鉴权并注入 claims | 最内层
<\/tbody>
<\/table> |
Recovery
go
import rcp "github.com/tx7do/go-wind-plugins/transport/grpc/middleware/recovery"
srv.Use(rcp.UnaryInterceptor(
rcp.WithStackTrace,rcp.WithLogger,))
捕获 panic → logger → 返回 codes.Internal
Logging
srv.Use(logmw.UnaryInterceptor(
logmw.WithSkipMethods,logmw.WithLogger,))
自动根据 status code 调整日志级别;支持跳过健康检查等噪声方法。
RequestID
srv.Use)
说到在业务代码里,
go
func Foo {
id := requestid.FromContext
fmt.Println
…}
Timeout
srv.Use(tmout.UnaryServerInterceptor(*time.Second。
tmout.WithSkipFunc bool{
return m == "/my.Service/LongRunning"
})))
若客户端未设置 deadline,则自动加上默认超时。
RateLimit
go
import (
rl "github.com/tx7do/go-wind-plugins/transport/grpc/middleware/ratelimit"
bucket "github.com/tx7do/go-wind-plugins/ratelimit/tokenbucket"
)
limiter,_ := bucket.New
srv.Use(rl.UnaryInterceptor(limiter,rl.WithSkipMethods))
AuthN
aunticator。_ := jwtAuth.NewAunticator))
srv.Use(authmw.UnaryInterceptor(aunticator,authmw.WithSkipMethods))
获取认证信息这方面,
go
claims,_ := engine.AuthClaimsFromContext
编写自定义拦截器
最简版
go
func methodLogMw(
ctx context.Context,req any,info *grpc.UnaryServerInfo,handler grpc.UnaryHandler,) {
fmt.Println
return handler
}…srv.Use
带配置的函数式选项
go
type optFn func
type opts struct{ strict bool }
func WithStrict optFn { return func{ o.strict=b } }
func MyValidator grpc.UnaryServerInterceptor {
cfg这方面。=&opts{}
for _,o:=range opts{ o}
return func(ctx context.Context,req any,info *grpc.UnaryServerInfo,handler grpc.UnaryHandler){
if v,ok:=req.error});ok{
if err:=v.Validate;err,=nil{
return nil,status.Error)
}
}
return handler
}
}
再看拦截器组合,Chain
当多个服务共享相同中间件集合时可使用全局链:
go
common := grpcServer.Chain(
recovery.UnaryInterceptor。requestid.UnaryServerInterceptor,logging.UnaryInterceptor,)
内部实现从后往前包装,以保持 “先注册 → 最外层”。
流式 RPC 与 Stream 拦截器
定义流式 Service 示例
proto
service Chat {
rpc Subscribe returns;rpc Chat returns;}
注册流式中间件
go
srv.UseStream(
recovery.StreamInterceptor,logging.StreamInterceptor,)
或使用创建选项:
go
grpcSrv := grpc_server.NewServer(":"。grpc_server.WithStreamMiddleware(
recovery.StreamInterceptor,logging.StreamInterceptor,),)
Stream 拦截器签名
go
func(
srv any,ss grpc.ServerStream,info *grpc.StreamServerInfo,handler grpc.StreamHandler,) error {…}
如果需要在 stream 中注入自定义 context,可包装 grpc.ServerStream
go
type wrapped struct{ grpc.ServerStream;ctx context.Context }
func Context context.Context { return w.ctx }
return func{
newCtx的观点是,=context.WithValue,"key","val")
再看ws。=&wrapped{ ServerStream:ss,ctx:newCtx}
return handler
}
gRPC 客户端
Go‑Wind 一样提供了轻量级客户端封装。
基本使用
go
c := client.NewClient
if err:=c.Dial);err,=nil{ panic}
defer c.Close
cli := pb.NewHelloServiceClient)
resp,_这方面,=cli.SayHello,&pb.HelloRequest{Name:"World"})
fmt.Println
客户端拦截器
go
c := client.NewClient("localhost:",client.WithMiddleware(
requestid.UnaryClientInterceptor,timeout.UnaryClientInterceptor。))
TLS 配置
go
creds,_:=credentials.NewClientTLSFromFile
c:=client.NewClient("example.com:",client.WithTransportCredentials)
生产级完整示例
以下示例整合了 限流、鉴权、日志、链路追踪、超时、恢复 六大必备功能,并演示了优雅关闭。
package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/google/wire"
"github.com/tx7do/go-wind-plugins/ratelimit/tokenbucket"
grpcAuthn "github.com/.../...authn"
"logmw "github.com/.../...logging"
"ratelimitMW github.com/.../...ratelimit"
recMW "github.com/.../...recovery"
reqIDMW "github.com/.../...requestid"
toMW "github.com/.../...timeout"
grpcSrv "github.com/.../...server"
jwtAuth "github.com/.../...jwt"
pb "...your/module/path/proto/hello"
)
type helloSrv struct{ pb.UnimplementedHelloServiceServer }
func SayHello{
id的观点是。=reqIDMW.FromContext
return &pb.HelloResponse{Message: fmt.Sprintf},nil
}
func main{
limiter,_:=tokenbucket.New
auth,_:=jwtAuth.NewAunticator))
srv的观点是,=grpcSrv.NewServer(":50051",grpcSrv.WithTimeout,)
srv.Use(
recMW.UnaryInterceptor,reqIDMW.UnaryServerInterceptor,logMW.UnaryInterceptor),toMW.Unary),ratelimitMW.Unary,authnMW.Unary,)
pb.RegisterHelloServiceServer,&helloSrv{})
ctx,sigStop:=signal.NotifyContext,syscall.SIGINT,syscall.SIGTERM)
defer sigStop
fmt.Printf)
if err:=srv.Start;err,=nil{
fmt.Fprintln
}
}
{Pain Point}: 在真实项目里经常忘记把 “限流”和 “鉴权” 放到最内层。导致它们被日志或恢复层吞掉,从而产生安全隐患。话说回来,<\/div>
使用 grpcurl 快速验证:
grpcurl -plaintext localhost:50051 list
TOKEN=$
grpcurl -plaintext -H \"authorization: Bearer $TOKEN\" \
-d '{\"name\":\"Bob\"}' \
localhost:50051 hello.HelloService/SayHello
<\/pre>
常见问题
Q1 | 服务返回 “Unimplemented” 错误?
原因实现结构体未嵌入对应的 UnimplementedXXX_Server。方法
go
type mySvc struct{ pb.UnimplementedMyServiceServer } // 必须嵌入!
Q2 | 自定义 interceptor 没生效?
排查清单
1️⃣ 确保 .Use 调用位于首次调用 Register... 或 .Start 前。2️⃣ 检查是否误用了 WithMiddleware 后又
调用 .Use 导致覆盖。3️⃣ 查看是否因方法名拼写错误导致 生效。
Q3 | 如何获取实际监听端口?
go
srv := grpcSrv.NewServer
fmt.Println) // => grpc://127.0.0.1:54321
Q4 | 只对部分方法跳过鉴权/限流?
所有内置 interceptor 均支持 参数。例如的观点是,
go
authMw := authnMw(UnifiedAunticator,authnMw.WithSkipMethods)
rateMw := ratelimitMw(limiter。ratelimitMw.WithSkipMethods)
svc.Use
Q5 | 客户端报 “connection refused”?
排查步骤
* ✅ 服务已成功启动且监听正确地址?* ✅ 客户端地址是否省略了协议前缀?* ✅ 若服务器启用了 TLS,请确保客户端也配置了对应凭证。* ✅ 使用随机端口时从服务器的 中读取实际端口再连接。
说到附录,API 速查表
Server 常用方法
| Method | Description
|
| NewServer | Create a new server instance. |
| >.Use<> | Add unary interceptors. |
| >.UseStream<> | Add stream interceptors. |
| >.RegisterService |
... continue ...
与 HTTP 教程对照表
| 概念 | HTTP | gRPC
|
| 创建服务器 | httpSvr.New
withDriver | grpcSrv.New |
...
<\/tbody>
<\/table>