Skip to content

Gateway

The pkg/gateway package makes a grpc-gateway a first-class transport. It dials the local gRPC server — matching the server's own transport security — and serves the generated REST handlers, either mounted on an existing HTTP server or as its own controller-managed HTTP server.

This gives you a JSON/REST surface over an existing gRPC service without standing up a separate, hand-written translation layer. The only gateway-specific code you write is a single registration function.

The RegisterFunc

The caller supplies a RegisterFunc that wires the generated gateway handlers onto the mux using a client connection to the gRPC server. This is the only gateway-specific code a caller writes, and it is typically a one-liner calling the generated RegisterXServiceHandler.

// RegisterFunc registers the generated gateway handlers onto the mux, using a
// client connection to the gRPC server.
type RegisterFunc func(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error

A realistic implementation:

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

Functions

  • New(ctx context.Context, cfg config.Containable, register RegisterFunc, opts ...Option) (http.Handler, error): Builds a grpc-gateway handler ready to mount on an existing HTTP server. It dials the local gRPC server via grpc.DialLocal (so transport security matches the server's own config) and applies register to wire the handlers. The returned handler is a *runtime.ServeMux, and the underlying gRPC connection lives for the process — the standard pattern for an in-process gateway.
  • Register(ctx context.Context, id string, controller controls.Controllable, cfg config.Containable, logger logger.Logger, register RegisterFunc, opts ...Option) (*http.Server, error): Runs the gateway as its own controller-managed HTTP server. It is the first-class form of New, a peer of grpc.Register and http.Register. Internally it builds the handler with New, then registers it via pkg/http's Register with WithConfigPrefix(ConfigPrefix).

Middleware on the REST surface

The gateway handler is an ordinary http.Handler, so the HTTP server middleware chain — logging, security headers, rate limiting, auth — applies to it like any other handler. Pass WithMiddleware to either entry point:

chain := gtbhttp.NewChain(
    gtbhttp.SecurityHeadersMiddleware(),
    gtbhttp.RateLimitMiddleware(log, gtbhttp.DefaultRateLimitConfig()),
)

// Managed server (Register): the chain wraps the REST routes; health endpoints
// (/healthz, /livez, /readyz) stay OUTSIDE it, exactly as with http.Register.
srv, _ := gateway.Register(ctx, "gateway", controller, cfg, log, registerFn,
    gateway.WithMiddleware(chain))

// Mount-on-existing-server (New): the chain wraps the returned handler directly.
handler, _ := gateway.New(ctx, cfg, registerFn, gateway.WithMiddleware(chain))
  • WithMiddleware(chain http.Chain) Option: wraps the gateway's REST surface with an HTTP middleware chain. On the Register path it is threaded to the managed server so health probes remain unauthenticated/unthrottled; on the New path it wraps the returned handler.

See the Transport Middleware & Resilience concept for the full pattern.

Configuration

When run via Register, the gateway server reads its own config block:

const ConfigPrefix = "server.gateway"

Port and TLS come from server.gateway.*, and TLS falls back to the shared server.tls defaults (following the same cascade as the other transports).

server:
  gateway:
    port: 8081
  tls:
    enabled: true
    cert: /etc/certs/server.crt
    key: /etc/certs/server.key

With the shared server.tls keys set and no server.gateway.tls override, the gateway server uses the same certificate as the rest of the stack.

Options

Option Description
WithMuxOptions(opts ...runtime.ServeMuxOption) Passes runtime.ServeMuxOption values to the gateway mux (e.g. a custom error handler or header matcher).
WithDialOptions(opts ...grpc.DialOption) Passes extra grpc.DialOption values to the connection the gateway opens to the gRPC server. Transport security is set automatically.
WithMiddleware(chain http.Chain) Wraps the REST surface with an HTTP middleware chain. On Register the chain wraps the routes while health endpoints stay outside it; on New it wraps the returned handler. See Middleware on the REST surface.

Usage Example: mounted on an existing server

Use New when the REST handlers should share an origin with other routes — for example, serving the API alongside the OpenAPI docs on a single mux.

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

gw, err := gateway.New(ctx, props.Config, register)
if err != nil {
    return err
}

mux := http.NewServeMux()
mux.Handle("/v1/", gw)            // REST handlers (annotations are /v1/...; do not strip the prefix)
mux.Handle("/docs/", docsHandler) // OpenAPI docs, same origin

srv, err := gtbhttp.Register(ctx, "http-api", controller, props.Config, props.Logger, mux)
if err != nil {
    return err
}

Usage Example: as its own server

Use Register to stand the gateway up as a controller-managed HTTP server on the server.gateway config block, peer to the gRPC and HTTP servers.

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

srv, err := gateway.Register(ctx, "gateway", controller, props.Config, props.Logger, register)
if err != nil {
    return err
}

Dependencies

The package builds on the rest of the web-service stack:

  • pkg/grpcDialLocal opens the connection to the local gRPC server with matching transport security.
  • pkg/httpRegister and WithConfigPrefix host the gateway as a controller-managed server when using Register.
  • grpc-gateway/v2 runtime — provides the ServeMux and the generated handler registration.