Connect Go

repository·main·Indexed 26 days ago

https://github.com/connectrpc/connect-go

A slim Go library for building HTTP APIs compatible with gRPC, gRPC-Web, and the Connect protocol. Built on the standard net/http library and Protocol Buffers, it provides a type-safe RPC framework for browsers, monoliths, and microservices. Includes the protoc-gen-connect-go plugin for code generation and supports unary, client-streaming, server-streaming, and bidirectional streaming RPCs.

Tokens
12.7K
Snippets
26
Records
95
Agent score
87%

What's inside connect-go

  1. Overview of Connect for Go

    main

    Connect is a slim library for building browser and gRPC-compatible HTTP APIs in Go. It uses Protocol Buffers to generate code for marshaling, routing, compression, and content type negotiation, as well as type-safe clients.

    Connect handlers and clients support three protocols:

    • Connect protocol: A simple protocol working over HTTP/1.1 or HTTP/2, designed to work in browsers, monoliths, and microservices. It is easily testable via curl using application/json.
    • gRPC
    • gRPC-Web

    Because Connect is built on top of the standard library, it is fully compatible with any package that works with net/http (http.Server, http.Client, or http.Handler).

  2. Implement a Connect Server in Go

    main

    To build a Connect server, follow these steps:

    1. Define your service in a .proto schema.
    2. Generate the Connect code.
    3. Implement the service interface by embedding the generated Unimplemented<Service>Handler struct.
    4. Register the handler using the generated constructor (e.g., New<Service>Handler) with an http.ServeMux.
    5. Use connect.WithInterceptors to add middleware like validation.

    Note: For production, refer to deployment guides for configuring timeouts, connection pools, and observability.

    package main
    
    import (
      "context"
      "log"
      "net/http"
    
      pingv1 "connectrpc.com/connect/internal/gen/connect/ping/v1"
      pingv1connect "connectrpc.com/connect/internal/gen/simple/connect/ping/v1/pingv1connect"
      "connectrpc.com/connect"
      "connectrpc.com/validate"
    )
    
    type PingServer struct {
      pingv1connect.UnimplementedPingServiceHandler // returns errors from all methods
    }
    
    func (ps *PingServer) Ping(ctx context.Context, req *pingv1.PingRequest) (*pingv1.PingResponse, error) {
      return &pingv1.PingResponse{
        Number: req.Number,
      }, nil
    }
    
    func main() {
      mux := http.NewServeMux()
      // The generated constructors return a path and a plain net/http
      // handler.
      mux.Handle(
        pingv1connect.NewPingServiceHandler(
          &PingServer{},
          // Validation via Protovalidate is almost always recommended
          connect.WithInterceptors(validate.NewInterceptor()),
        ),
      )
      p := new(http.Protocols)
      p.SetHTTP1(true)
      // For gRPC clients, it's convenient to support HTTP/2 without TLS.
      p.SetUnencryptedHTTP2(true)
      s := &http.Server{
        Addr:      "localhost:8080",
        Handler:   mux,
        Protocols: p,
      }
      if err := s.ListenAndServe(); err != nil {
        log.Fatalf("listen failed: %v", err)
      }
    }
  3. Understand the Connect/gRPC wire format envelope

    main

    The Connect and gRPC protocols use an envelope to frame messages. Each message is preceded by a 5-byte prefix:

    • Byte 0: A uint8 bitwise flag field. One specific flag is flagEnvelopeCompressed (0x01), indicating the payload is compressed.
    • Bytes 1-4: A uint32 in Big Endian format representing the message length.

    While the framing is consistent, gRPC and Connect interpret the bitwise flags in the first byte differently. The envelope type provides methods to interact with this framed data as an io.Reader, io.WriterTo, and io.Seeker.

  4. Connect Protocol Version Requirements

    main

    To ensure compatibility, Connect requests must include the protocol version. The method of providing this version depends on the HTTP method used:

    • GET requests: The version must be provided as a query parameter: connectVersion=connect-v1.
    • POST requests: The version must be provided in the header: connect-protocol-version: connect-v1.

    If the version is missing or incorrect, the server should return a CodeInvalidArgument error.

  5. Use protoc-gen-connect-go to generate Connect code

    main

    The protoc-gen-connect-go plugin generates Go code for Connect services. To use it, build the program and ensure it is available on your PATH as protoc-gen-connect-go.

    Using with protoc

    To generate both the base Go types (using protoc-gen-go) and the Connect service code, use the following command structure:

    protoc --go_out=gen --connect-go_out=gen path/to/file.proto

    Using with Buf

    In your buf.gen.yaml, configure the plugins as follows:

    version: v2
    plugins:
      - local: protoc-gen-go
        out: gen
      - local: protoc-gen-connect-go
        out: gen

    Output File Structure

    If file.proto defines the foov1 Protobuf package, the output will be written to:

    • gen/path/to/file.pb.go (Base types)
    • gen/path/to/foov1connect/file.connect.go (Connect service code)
    protoc --go_out=gen --connect-go_out=gen path/to/file.proto
  6. Test Connect APIs with buf curl (gRPC protocol)

    main

    You can use the buf CLI to call Connect services using the gRPC protocol.

    go install github.com/bufbuild/buf/cmd/buf@latest
    buf curl --protocol grpc \
        --data '{"sentence": "I feel happy."}' \
        https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
  7. Implement a Connect Client in Go

    main

    To call a Connect service from a Go client, use the generated client constructor (e.g., New<Service>Client). You must provide an http.Client and the base URL of the server.

    package main
    
    import (
      "context"
      "log"
      "net/http"
    
      pingv1 "connectrpc.com/connect/internal/gen/connect/ping/v1"
      pingv1connect "connectrpc.com/connect/internal/gen/simple/connect/ping/v1/pingv1connect"
    )
    
    func main() {
      client := pingv1connect.NewPingServiceClient(
        http.DefaultClient,
        "http://localhost:8080/",
      )
      req := &pingv1.PingRequest{Number: 42}
      res, err := client.Ping(context.Background(), req)
      if err != nil {
        log.Fatalln(err)
      }
      log.Println(res)
    }
  8. Test Connect APIs with curl

    main

    Since the Connect protocol supports JSON over HTTP, you can call services using curl by setting the Content-Type to application/json and providing the request body in the --data flag.

    curl \
        --header "Content-Type: application/json" \
        --data '{"sentence": "I feel happy."}' \
        https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
  9. Configure protoc-gen-connect-go flags

    main

    The protoc-gen-connect-go plugin supports the following configuration options:

    package_suffix

    Determines the sub-package name where Connect code is generated relative to the base .pb.go files.

    • Default: "connect" (e.g., foov1connect).
    • To generate into the same package as the base .pb.go files, provide an empty string for the suffix.

    simple

    Generates client and handler interfaces with simplified function signatures.

    • Default: false.
    • When true: Eliminates the connect.Request and connect.Response wrapper types. Instead, functions use the generated RPC request/response types directly, and context.Context is used to propagate metadata like headers. This is often more familiar to standard Go developers.

    Example: Generating into the same package

    To generate Connect code into the same package as your base Protobuf types, use the package_suffix option with an empty value (in Buf, this is done by setting the option to an empty string or omitting it if the plugin supports it, but specifically via opt in protoc or buf.gen.yaml):

    version: v2
    plugins:
      - local: protoc-gen-go
        out: gen
      - local: protoc-gen-connect-go
        out: gen
        opt: package_suffix
  10. Create a Simple Unary RPC Handler

    main
    Use NewUnaryHandlerSimple to create a handler for a request-response procedure using a simplified function signature. This version eliminates the connect.Request and connect.Response wrappers, instead using the raw request/response types and propagating metadata via context.Context.
  11. Extend Codec with MarshalAppend

    main

    If you want to optimize memory allocations, you can implement the marshalAppender interface. This allows a codec to append marshaled data directly to an existing byte slice, avoiding extra allocations.

    Implement the MarshalAppend([]byte, any) ([]byte, error) method to support this optimization.

    type marshalAppender interface {
    	Codec
    	MarshalAppend([]byte, any) ([]byte, error)
    }