cmux

repository·master·Indexed 23 days ago

https://github.com/soheilhy/cmux

A generic Go library used to multiplex multiple protocols, such as gRPC, SSH, and HTTP, over a single TCP listener by inspecting the initial payload of each connection. It provides various matchers for HTTP/1, HTTP/2, and TLS, and supports custom matching rules via Match and MatchWithWriters to route connections to specific sub-listeners.

Tokens
1.7K
Snippets
2
Records
21
Agent score
80%

What's inside cmux

  1. Multiplex multiple protocols on a single TCP listener with cmux

    master

    You can use cmux to serve multiple protocols (such as gRPC, HTTP, and Go RPC) on the same TCP port by creating a cmux instance from a standard net.Listener and defining matching rules for each protocol. The muxer matches connections in the order they are defined.

    // Create the main listener.
    l, err := net.Listen("tcp", ":23456")
    if err != nil {
    	log.Fatal(err)
    }
    
    // Create a cmux.
    m := cmux.New(l)
    
    // Match connections in order:
    // First grpc, then HTTP, and otherwise Go RPC/TCP.
    grpcL := m.Match(cmux.HTTP2HeaderField("content-type", "application/grpc"))
    httpL := m.Match(cmux.HTTP1Fast())
    trpcL := m.Match(cmux.Any()) // Any means anything that is not yet matched.
    
    // Create your protocol servers.
    grpcS := grpc.NewServer()
    grpchello.RegisterGreeterServer(grpcS, &server{})
    
    httpS := &http.Server{
    	Handler: &helloHTTP1Handler{},
    }
    
    trpcS := rpc.NewServer()
    trpcS.Register(&ExampleRPCRcvr{})
    
    // Use the muxed listeners for your servers.
    go grpcS.Serve(grpcL)
    go httpS.Serve(httpL)
    go trpcS.Accept(trpcL)
    
    // Start serving!
    m.Serve()
  2. Handle Java gRPC clients using MatchWithWriters

    master

    Java gRPC clients block until they receive a SETTINGS frame from the server. To support Java gRPC clients when using cmux, you must use MatchWithWriters instead of a standard Match to ensure the client can receive the necessary frames.

    grpcl := m.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
  3. Limitations of cmux

    master

    When using cmux, be aware of the following limitations:

    • TLS: Because cmux wraps the underlying connection to implement lookahead, net/http type assertions for TLS will fail. While you can serve HTTPS, http.Request.TLS will not be populated in your handlers.
    • Protocol Switching: cmux matches a connection at the moment it is accepted. A single connection cannot switch protocols (e.g., a connection cannot be both gRPC and REST); it must be one or the other.
    • Java gRPC Clients: Requires the use of MatchWithWriters as described in the documentation to prevent client blocking.
  4. Start the CMux server

    master
    Call Serve() on the CMux instance to begin multiplexing. Serve() is a blocking call and should typically be invoked in its own goroutine to allow the main application to continue or to manage multiple listeners.
  5. Create sub-listeners using MatchWithWriters

    master

    Use MatchWithWriters(...MatchWriter) when a matcher needs to write data to the connection (e.g., to perform a handshake) before handing the connection off to the final handler.

    A MatchWriter has the signature func(io.Writer, io.Reader) bool.

    Note: Prefer Match (using Matcher) over MatchWithWriters whenever possible, as MatchWithWriters can modify the connection state before the actual handler receives it.

  6. Set a read timeout for matchers

    master
    Use SetReadTimeout(time.Duration) to define how long the multiplexer will wait to read data from a connection to determine which matcher applies. This prevents slow or malicious clients from hanging the multiplexing process.
  7. Create sub-listeners using Matchers

    master

    The Match(...Matcher) method returns a net.Listener that only accepts connections matching the provided Matcher functions. Matchers are evaluated in the order they are passed, establishing priority.

    A Matcher is a function with the signature func(io.Reader) bool used to inspect connection content without consuming it permanently.

  8. Initialize a new CMux multiplexer

    master
    Use cmux.New(l net.Listener) to create a new connection multiplexer from an existing network listener. This multiplexer will manage incoming connections and route them to specific sub-listeners based on matchers you define.
  9. Configure error handling in CMux

    master
    Register a custom ErrorHandler using HandleError(ErrorHandler). The handler receives the error and returns a bool: if true, the mux continues serving; if false, the mux stops.
  10. Handle CMux error types

    master

    When working with cmux, you may encounter these specific error types:

    • ErrNotMatched: Returned when a connection does not match any registered matchers. It implements net.Error and is marked as Temporary() == true.
    • ErrListenerClosed: Returned from Accept() when the underlying listener is closed.
    • ErrServerClosed: Returned from Accept() when the mux server is closed.