gRPC服务默认不支持HTTP/1.1,需通过h2c启用HTTP/2明文通信;curl需加--http2且服务端用h2c.NewHandler包装;客户端DeadlineExceeded多因拦截器阻塞或超时设置过短;proto变更需手动触发go generate。

Go 的 gRPC 服务默认不支持 HTTP/1.1 请求,直接用浏览器访问 /health 或 curl GET 会返回 404 Not Found 或 HTTP/2 stream error —— 这不是代码写错了,是协议层根本对不上。
为什么 grpc.Server 启动后 curl 报错 “connection refused” 或 “protocol error”
gRPC 基于 HTTP/2,而 curl 默认走 HTTP/1.1;即使加 --http2,也需配合 TLS(或显式启用 h2c)才能连上非加密 gRPC 端口。常见表现:
- 未启用 h2c 时,
curl -v http://localhost:8080直接被拒绝或返回空响应 - 用
grpcurl测试却提示Failed to dial target host,大概率是没开Reflection或端口被防火墙拦截 - Go 服务监听
:0(随机端口)但没打印实际绑定地址,导致客户端连错
实操建议:
- 启动服务时强制指定端口并打印日志:log.Printf("gRPC server listening at %v", lis.Addr())
- 非生产环境快速验证,启用 h2c:grpcServer := grpc.NewServer(grpc.ProtocolVersion(1)) 不行 —— 正确做法是在 http.Server 中用 h2c.NewHandler() 包裹 gRPC handler(见下条)
如何让 gRPC Server 同时支持 h2c(HTTP/2 without TLS)
Go 标准库的 net/http 默认不支持 h2c,必须手动桥接。核心是把 grpc.Server 注册为 http.Handler,再用 h2c 中间件透传 HTTP/2 流量。
关键步骤:
立即学习“go语言免费学习笔记(深入)”;
- 导入
"golang.org/x/net/http2/h2c" - 创建
http.Server,将grpcServer.ServeHTTP作为 handler:handler := h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { grpcServer.ServeHTTP(w, r) }), &h2c.Server{}) - 监听时不要用
grpcServer.Serve(lis),改用httpServer.Serve(lis) - 注意:此时不能再调用
grpcServer.GracefulStop(),需同步控制http.Server的生命周期
这样之后,就能用 curl --http2 -X POST http://localhost:8080/my.Service/Method -d '{"id":1}'(需 JSON mapping)或 grpcurl -plaintext localhost:8080 list 直接调试。
客户端连接时 context.DeadlineExceeded 的真实原因
这个错误常被误认为网络不通,其实多数情况是服务端未及时响应 —— 尤其在启用了拦截器(interceptor)、中间件或鉴权逻辑阻塞时。典型场景:
- 服务端
UnaryInterceptor里做了同步 HTTP 调用,且对方响应慢 - 客户端
grpc.WithTimeout(1 * time.Second)设得太短,而服务端处理耗时 1.2s - DNS 解析失败或
resolver配置错误,导致连接卡在Connecting状态超时
排查建议:
- 客户端加日志:用 grpc.WithStatsHandler(&customStatsHandler{}) 观察各阶段耗时
- 服务端拦截器开头打时间戳,确认是否卡在业务逻辑前
- 检查 resolver 是否用了自定义方案(如 dns:/// 或 etcd),避免解析延迟
Proto 文件变更后,为什么 go generate 不触发重新生成 pb.go
Go 的 //go:generate 是静态指令,不会自动感知 .proto 文件修改。常见陷阱:
- 执行
go generate ./...时,当前目录下没有go:generate注释,导致跳过 -
protoc命令路径未加入$PATH,或protoc-gen-go版本与google.golang.org/protobuf不兼容(如 v1.31+ 需要 protoc-gen-go v1.32+) -
import路径写成相对路径(如import "proto/common.proto"),但protoc --proto_path=.未覆盖该目录
可靠做法:
- 在 api/ 目录下放一个 generate.go,含完整 //go:generate protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. *.proto
- 提交前跑一次 make proto(Makefile 封装命令),比依赖 IDE 自动生成更可控
最易忽略的一点:gRPC 的流式接口(stream)一旦客户端提前关闭 context,服务端 Send() 可能 panic,必须用 if err != nil && status.Code(err) == codes.Canceled 显式判断,而不是只看 err != nil。










