Skip to content

Gateway reference

gitlab.com/phpboyscout/go/transport/gateway wraps a grpc-gateway mux so a gRPC service is also reachable over REST/JSON. It is a thin package: three options, two entry points, and a clear rule about who owns the gRPC connection.

Entry points

func New(ctx context.Context, conn *grpc.ClientConn, register RegisterFunc,
    opts ...Option) (http.Handler, error)

func Register(ctx context.Context, id string, controller controls.Controllable,
    logger *slog.Logger, conn *grpc.ClientConn,
    httpSettings transporthttp.ServerSettings, httpTLS gtls.Pair,
    register RegisterFunc, opts ...Option) (*http.Server, error)
type RegisterFunc func(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error

RegisterFunc is the only gateway-specific code you write — it calls the registration function protoc generated for your service:

func(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
    return widgetv1.RegisterWidgetServiceHandler(ctx, mux, conn)
}

New versus Register: what each one owns

New Register
Returns a plain http.Handler a running, supervised *http.Server
Serves nothing — you serve it its own HTTP listener via transporthttp.Register
Health endpoints none /healthz, /livez, /readyz, outside the middleware chain
Middleware application point wraps the returned handler directly threaded into the managed server
Owns the *grpc.ClientConn no — you close it yes — do not close it yourself

Because the middleware application point differs, so does what it covers: on the New path the chain wraps only the gateway handler, because there are no health endpoints to keep out of it. On the Register path the chain is handed to the managed HTTP server, which mounts health endpoints outside it — exactly as transporthttp.Register does for any other handler.

Who closes the gRPC connection

Register takes ownership of conn. It closes it if it returns an error, and registers a controller stop hook so it is closed on shutdown. Closing it yourself after calling Register is a bug.

The stop hook is registered under id + ":conn", before the HTTP server is registered. The controller stops services in reverse order, so the HTTP server drains first and the gRPC connection is closed only afterwards — the gateway never dials a closed connection mid-drain. The close is guarded by a sync.Once, so the error path and the stop hook cannot double-close (which gRPC reports as ErrClientConnClosing).

New takes no ownership: the caller both serves the handler and closes the connection.

Options

Option Default Effect
WithMuxOptions(...runtime.ServeMuxOption) none Passed to runtime.NewServeMux — a custom error handler, header matcher, marshaler, and so on. Accumulates across calls.
WithDialOptions(...grpc.DialOption) none Extra dial options for the gateway's outbound gRPC connection. Accumulates across calls.
WithMiddleware(transithttp.Chain) none An HTTP middleware chain over the REST surface. Last call wins.

WithDialOptions is the mTLS escape hatch

Transport security for the gateway's dial is selected automatically. When the upstream gRPC server requires client certificates, that automatic selection is not enough: pass grpc.WithTransportCredentials(credentials.NewTLS(cfg)) with a client *tls.Config carrying the gateway's own client certificate and the CA pool that signed the server's certificate. An explicit transport-credentials option overrides the automatically selected one.

Settings

type Settings struct {
    HTTP    transporthttp.ServerSettings `yaml:"http"     json:"http"`
    HTTPTLS gtls.Pair                    `yaml:"http_tls" json:"http_tls"`
    GRPC    transportgrpc.ServerSettings `yaml:"grpc"     json:"grpc"`
    GRPCTLS gtls.Pair                    `yaml:"grpc_tls" json:"grpc_tls"`
}

A gateway spans two listeners — its own HTTP one and the upstream gRPC one — so Settings is assembled by a caller from two config blocks, not decoded from one section. That is why it carries yaml/json tags but no mapstructure tags: the tags exist for documentation and snapshot serialisation, and nothing in this package decodes into the struct.

Note that Register does not take a Settings. It takes httpSettings and httpTLS directly, because by the time you call it the gRPC side is already a live *grpc.ClientConn. Settings is the shape a config adapter resolves before dialling.

The gateway does not dial for you

New and Register both require an already-prepared *grpc.ClientConn. This package contains no dial logic. Use transportgrpc.DialLocal for an in-process server, or go/grpcclient for a remote one.

Errors

The package defines no error values of its own. What it returns is:

  • whatever your RegisterFunc returns — a generated handler registration failing, typically a nil connection or a duplicate pattern;
  • whatever transporthttp.Register returns on the Register path — an invalid port, a bad served certificate, or an unsupported option.

On either failure Register closes conn before returning.