#development #golang #pattern

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:

 1package main
 2
 3import (
 4    "log"
 5    "net"
 6    "net/http"
 7
 8    "github.com/soheilhy/cmux"
 9    "golang.org/x/sync/errgroup"
10    "google.golang.org/grpc"
11)
12
13func main() {
14
15    // Create the listener
16    l, err := net.Listen("tcp", ":8080")
17    if err != nil {
18        log.Fatal(err)
19    }
20    
21    // Create a new cmux instance
22    m := cmux.New(l)
23
24    // Create a grpc listener first
25    grpcListener := m.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
26    
27    // All the rest is assumed to be HTTP
28    httpListener := m.Match(cmux.Any())
29
30    // Create the servers
31    grpcServer := grpc.NewServer()
32    httpServer := &http.Server{}
33
34    // Use an error group to start all of them
35    g := errgroup.Group{}
36    g.Go(func() error {
37        return grpcServer.Serve(grpcListener)
38    })
39    g.Go(func() error {
40        return httpServer.Serve(httpListener)
41    })
42    g.Go(func() error {
43        return m.Serve()
44    })
45
46    // Wait for them and check for errors
47    err = g.Wait()  
48    if err != nil {
49        log.Fatal(err)
50    }
51
52}

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.