My current architecture is a mix of static files and REST endpoints and I want to migrate from REST to gRPC - which is working quite well in itself. To keep the number of processes low, I would like to serve the static files AND the gRPC-web from a single process, but behind different routes (e.g. /static and /grpc).

Can I somehow "serve" gRPC from a route in gin?

In the example below http.Server variant works fine, but the GIN Server variant does nothing except logging the request.

func (gs *GRPCWebServer) Init(servePort int, path string) {

    gs.port = servePort

    grpcServer := grpc.NewServer()
    library.RegisterPermissionServiceServer(grpcServer, newPermissionService(path))
    grpclog.SetLogger(log.New(os.Stdout, "grpc: ", log.LstdFlags))

    wrappedServer := grpcweb.WrapServer(grpcServer)

    // http.Server variant
    //
    // handler := func(resp http.ResponseWriter, req *http.Request) {
    //  wrappedServer.ServeHTTP(resp, req)
    // }
    // httpServer := http.Server{
    //  Addr:    fmt.Sprintf(":%d", gs.port),
    //  Handler: http.HandlerFunc(handler),
    // }
    // grpclog.Printf("Starting gRPC-web server, port: %d", gs.port)
    // if err := httpServer.ListenAndServe(); err != nil {
    //  grpclog.Fatalf("failed starting gRPC-web server: %v", err)
    // }

    // GIN Server variant
    //
    ginServer := gin.New()
    ginServer.Use(gin.Logger())
    ginServer.Use(gin.Recovery())
    ginServer.Group("/grpc", func(c *gin.Context) {
        wrappedServer.ServeHTTP(c.Writer, c.Request)
    })
    ginServer.Run(fmt.Sprintf(":%d", servePort+1))
}

Comment From: VoyTechnology

I recommend using https://github.com/soheilhy/cmux. I've used it on plain gRPC with a webserver running on the same port and it worked fine, although I am unsure how well it would work with grpc-web

Comment From: ykensuke

This is how I implemented in order to run gin and grpcweb servers on the same port. It seems be working fine so far.

func main() {
    http.ListenAndServe(":8000", NewHandler())
}

type Handler struct {
    ginHandler     *gin.Engine
    grpcwebHandler *grpcweb.WrappedGrpcServer
}

func NewHandler() *Handler {
    router := gin.Default()
    router.GET("/", handleFunc)

    grpcServer := grpc.NewServer()
    library.RegisterPermissionServiceServer(grpcServer, newPermissionService(path))
    wrappedServer := grpcweb.WrapServer(grpcServer)

    return &Handler{
        ginHandler:     router,
        grpcwebHandler: wrappedServer,
    }
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    contentType := req.Header.Get("Content-Type")
    if contentType == "application/grpc-web+proto" {
        h.grpcwebHandler.ServeHTTP(w, req)
        return
    }
    h.ginHandler.ServeHTTP(w, req)
}

I've tried cmux but I finally gave up setting up it. I don't know how it should be set up and if it's possible. Both of gin and grpcweb are http2 based. cmux doesn't seem to be needed.

Comment From: a67793581

I recommend using https://github.com/soheilhy/cmux. I've used it on plain gRPC with a webserver running on the same port and it worked fine, although I am unsure how well it would work with grpc-web 还可以使用这个方法 router.Use(func(ctx *gin.Context) { if ctx.Request.ProtoMajor == 2 && strings.HasPrefix(ctx.GetHeader("Content-Type"), "application/grpc") { ctx.Status(http.StatusOK) grpcServer.ServeHTTP(ctx.Writer, ctx.Request) ctx.Abort() return } ctx.Next() }) router.UseH2C = true srv := &http.Server{ Addr: fmt.Sprintf(":%d", config.GetConfig().HttpPort), Handler: router.Handler(), }