Combining gRPC and HTTP on the same port

May 13, 2020
pattern | module

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.