Go中使用grpc-gateway同端口运行GRPC和HTTP服务时server preface报错如何解决?
问题根因
- gRPC客户端连接初始化时机错误
你在makeGrpcServer函数中创建完gRPC服务端实例后,直接调用grpc.DialContext拨号当前服务的监听地址,此时gRPC服务还未正式启动承接请求,建立的客户端连接属于无效连接,服务启动初期使用该连接转发请求就会触发preface接收失败的错误,等服务完全启动后新建立的连接恢复正常,所以重试几次就可以正常访问。
- gRPC客户端连接初始化时机错误
- gRPC/HTTP路由逻辑错误
你在httpGrpcRouter中判断为HTTP2请求时直接调用grpcServer.Serve(listener),该方法是阻塞方法,会独占监听端口处理gRPC请求,不仅会导致后续HTTP请求无法被处理,也不符合单端口复用的正确实现逻辑,会随机出现连接状态异常。
- gRPC/HTTP路由逻辑错误
- 代码笔误
makeHttpServer函数中第一次注册handler后出现了无效字符nricher,会导致编译失败,需要修正。
- 代码笔误
修复方案
修改路由判断逻辑、调整gRPC客户端连接初始化时机,修复后的完整代码如下:
import "strings" func makeHttpServer(conn *grpc.ClientConn) *runtime.ServeMux { router := runtime.NewServeMux() if err := services.RegisterHealthServiceHandler(context.Background(), router, conn); err != nil { log.Logger.Error("Failed to register gateway", zap.Error(err)) } if err := services.RegisterConstraintsServiceHandler(context.Background(), router, conn); err != nil { log.Logger.Error("Failed to register gateway", zap.Error(err)) } return router } func makeGrpcServer() *grpc.Server { grpcServer := grpc.NewServer() services.RegisterHealthServiceServer(grpcServer, health.Svc{}) services.RegisterABCServer(grpcServer, ABC.Svc{}) return grpcServer } func httpGrpcRouter(grpcServer *grpc.Server, httpHandler *runtime.ServeMux) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 正确判断gRPC请求:HTTP2协议 + Content-Type前缀为application/grpc if r.ProtoMajor == 2 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") { grpcServer.ServeHTTP(w, r) } else { httpHandler.ServeHTTP(w, r) } }) } func Start() error { conf := config.Get() address := fmt.Sprintf("%s:%d", conf.ServerHost, conf.ServerPort) listener, err := net.Listen("tcp", address) if err != nil { log.Logger.Fatal("failed to listen: %v", zap.Error(err)) } grpcServer := makeGrpcServer() // 等端口监听后再初始化gRPC网关的客户端连接 dialCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() conn, err := grpc.DialContext( dialCtx, address, grpc.WithInsecure(), grpc.WithBlock(), // 阻塞等待连接建立完成,避免拿无效连接 ) if err != nil { log.Logger.Error("Failed to dial server", zap.Error(err)) return err } defer conn.Close() router := makeHttpServer(conn) log.Logger.Info("Starting server on address : " + address) err = http.Serve(listener, httpGrpcRouter(grpcServer, router)) return err }
额外优化建议
- 生产环境建议替换
grpc.WithInsecure()为对应的TLS配置 - 可以根据业务场景调整拨号阶段的超时时长,适配不同启动速度的服务
内容的提问来源于stack exchange,提问作者Kshitij Singh
相关产品推荐
相关产品推荐

