If you ever have the need to combine a plain HTTP webserver with a gRPC server on the same port, then you can use cmux to do so.

You can do something like:

package main
 
import (
"log"
"net"
"net/http"
 
"github.com/soheilhy/cmux"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
)
 
func main() {
 
// Create the listener
l, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
 
// Create a new cmux instance
m := cmux.New(l)
 
// Create a grpc listener first
grpcListener := m.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
 
// All the rest is assumed to be HTTP
httpListener := m.Match(cmux.Any())
 
// Create the servers
grpcServer := grpc.NewServer()
httpServer := &http.Server{}
 
// Use an error group to start all of them
g := errgroup.Group{}
g.Go(func() error {
return grpcServer.Serve(grpcListener)
})
g.Go(func() error {
return httpServer.Serve(httpListener)
})
g.Go(func() error {
return m.Serve()
})
 
// Wait for them and check for errors
err = g.Wait()
if err != nil {
log.Fatal(err)
}
 
}

This also demonstrates how the errgroup module can be used. The Wait method blocks until all function calls from the Go method have returned, then returns the first non-nil error (if any) from them.

PS: the example itself doesn't actually do something useful as we didn't register any gRPC functions neither did I define HTTP handlers.