You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go中使用grpc-gateway同端口运行GRPC和HTTP服务时server preface报错如何解决?

问题根因

    1. gRPC客户端连接初始化时机错误
      你在makeGrpcServer函数中创建完gRPC服务端实例后,直接调用grpc.DialContext拨号当前服务的监听地址,此时gRPC服务还未正式启动承接请求,建立的客户端连接属于无效连接,服务启动初期使用该连接转发请求就会触发preface接收失败的错误,等服务完全启动后新建立的连接恢复正常,所以重试几次就可以正常访问。
    1. gRPC/HTTP路由逻辑错误
      你在httpGrpcRouter中判断为HTTP2请求时直接调用grpcServer.Serve(listener),该方法是阻塞方法,会独占监听端口处理gRPC请求,不仅会导致后续HTTP请求无法被处理,也不符合单端口复用的正确实现逻辑,会随机出现连接状态异常。
    1. 代码笔误
      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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 00:36:06