This is a pretty tricky problem to identify. Since browsers prefer to use an existing TCP connection for pipelining, the mux tends to send packets to the wrong protocol handler, for example it could send grpc-web
packets to REST, and vice versa. Luckily, there is a pretty simple solution for this:
package main
listener := net.Listen("tcp", ":2289")
multiplexer := cmux.New(listener)
grpcListener := multiplexer.Match(cmux.HTTP2())
httpListener := multiplexer.Match(cmux.HTTP1Fast())
grpcServer := grpc.Server()
wrappedGrpc := grpcweb.WrapServer(grpcServer)
go func() {
httpServer := echo.New()
(&http.Server{
Handler: http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if strings.Contains(req.Header.Get("Access-Control-Request-Headers"), "x-grpc-web") || req.Header.Get("x-grpc-web") == "1" || req.Header.Get("Sec-Websocket-Protocol") == "grpc-websockets" {
inst.API.GrpcWebServer.ServeHTTP(resp, req)
} else {
httpServer.ServeHTTP(resp, req)
}
}),
}).Serve(httpListener)
}()
go func() {
grpcServer.Serve(grpcListener)
}()
go func() {
multiplexer.Serve()
}()
How does this work?
Essentially, instead of using cmux
's default muxing (which only muxes per-connection) we registered a new mini http server handler on all HTTP requests coming in, which then lets us explicitly check headers and call handlers directly.