router

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package router transparently forwards inbound gRPC traffic to an upstream connection without decoding it.

It provides two pieces that are wired onto the inbound server:

Together they let the server forward any method it does not handle locally, with no knowledge of the underlying protobuf messages. The name anticipates selecting the upstream connection per request; today Handler targets a single connection.

Module wires both into an fx application, providing the codec and the handler (with the upstream connection to the proxy socket built from configuration and closed on shutdown). Consumers depend on the provided types rather than importing this package directly.

Index

Constants

This section is empty.

Variables

View Source
var Module = fx.Options(fx.Provide(
	Codec,
	func(p RouterParams) (grpc.StreamHandler, error) {
		conns := make(map[string]*grpc.ClientConn, len(p.Config.Upstreams))
		for i := range p.Config.Upstreams {
			upstream := &p.Config.Upstreams[i]
			sockPath, err := socket.UnixPath(upstream.Listen.HostPort)
			if err != nil {
				return nil, fmt.Errorf("failed to resolve proxy socket path[%q]: %w", upstream.Name, err)
			}

			sock := "unix://" + sockPath
			conn, err := p.Pool.ConnOrCreate(sock, sock, grpc.WithTransportCredentials(insecure.NewCredentials()))
			if err != nil {
				return nil, fmt.Errorf("failed to create upstream client[%q]: %w", upstream.Name, err)
			}

			conns[upstream.Name] = conn
		}

		return Handler(
			&director{
				conns:    conns,
				mux:      p.Mux,
				reporter: p.Reporter,
			},
			p.Extractor,
			p.Reporter,
		), nil
	},
	func(c *config.Config, f *metrics.Factory) *Reporter {
		names := make([]string, 0, len(c.Upstreams))
		for i := range c.Upstreams {
			names = append(names, c.Upstreams[i].Name)
		}

		return NewReporter(f.ForSubsystem("router"), names)
	},
	func(c *config.Config) (*Mux, error) {
		rules := make([]Rule, 0, len(c.Routing.Rules))
		for i, r := range c.Routing.Rules {
			p := r.Match.Namespace
			if p == "" {
				p = "*"
			}

			ns, err := match.Compile(p)
			if err != nil {
				return nil, fmt.Errorf("routing: rules[%d].match.namespace: %w", i, err)
			}

			meta := make(map[string]Matcher, len(r.Match.Metadata))
			seen := make(map[string]string, len(r.Match.Metadata))
			for k, v := range r.Match.Metadata {
				lk := strings.ToLower(k)
				if prev, ok := seen[lk]; ok {
					return nil, fmt.Errorf(
						"routing: rules[%d].match.metadata: keys %q and %q both map to %q when lowercased",
						i, prev, k, lk,
					)
				}

				seen[lk] = k
				m, err := match.Compile(v)
				if err != nil {
					return nil, fmt.Errorf("routing: rules[%d].match.metadata[%q]: %w", i, k, err)
				}

				meta[lk] = m
			}

			rules = append(rules, Rule{
				upstream: r.Upstream,
				ns:       ns,
				meta:     meta,
			})
		}

		return New(
			c.Routing.DefaultUpstream,
			c.Routing.SystemUpstream,
			rules...,
		), nil
	},
))

Module is the fx module that provides the routing-and-forwarding pieces: a pass-through google.golang.org/grpc/encoding.CodecV2, a Mux compiled from the routing configuration, and a google.golang.org/grpc.StreamHandler. The handler dials one connection per configured upstream from the shared connect.Pool (each unix socket path derived from that upstream's host:port), then routes every request to an upstream by matching it with the Mux.

Functions

func Codec

func Codec() encoding.CodecV2

Codec returns the hybrid pass-through codec. It must be applied per-call via grpc.ForceServerCodecV2 / grpc.ForceCodecV2; it is deliberately not registered globally so it never shadows the real proto codec process-wide.

func Handler

func Handler(d Director, r Reflector, rep *Reporter) grpc.StreamHandler

Handler returns a grpc.StreamHandler suitable for grpc.UnknownServiceHandler, reporting a stream_setup forwarding error via rep when opening the upstream stream fails. It buffers the first request frame so r can peek the request namespace, asks d for the upstream connection, then transparently forwards the stream to that upstream using the same full method name: it replays the buffered first frame, pumps raw frames in both directions, and propagates header, trailer, and status verbatim.

func StatusError

func StatusError(err error) error

StatusError maps a request-pump error to the gRPC status returned to the caller. It forwards an error that already carries a gRPC status verbatim, maps a raw context error to its status, and otherwise reports Internal.

Types

type Director

type Director interface {
	Resolve(ctx context.Context, method, namespace string, md map[string][]string) (Target, error)
}

Director selects the upstream for a request. Resolve receives the full method, the namespace peeked from the first request message (empty when the client sent no message), and the incoming metadata, and returns the Target to forward over. A non-nil error aborts the stream and is returned to the caller verbatim, so implementations should return a gRPC status error.

type Matcher

type Matcher interface {
	Match(string) bool
}

Matcher reports whether a string satisfies some pattern. Rules use it to match namespaces and metadata values, keeping Mux decoupled from any particular matching implementation.

type Mux

type Mux struct {
	// contains filtered or unexported fields
}

Mux selects the upstream that serves a request by matching it against an ordered list of rules. It holds upstream names only, not connections, so callers map the name Switch returns to a connection. A Mux is read-only after construction and safe for concurrent use.

func New

func New(defUpstream, sysUpstream string, rules ...Rule) *Mux

New returns a Mux that evaluates rules in order. defUpstream is returned when no rule matches; sysUpstream, when non-empty, serves a request that carries no namespace and matches no rule. Either name may be empty, in which case Switch can return "" to signal that the request is unroutable.

func (*Mux) Switch

func (m *Mux) Switch(ns string, md map[string][]string) (string, Outcome)

Switch returns the upstream that serves a request with the given namespace and metadata, and the Outcome describing why it was chosen. Rules are evaluated in order and the first match wins. With no matching rule, a request with no namespace goes to the system upstream if configured, and every other request goes to the default upstream. An empty result (the selected upstream is unset) is reported as OutcomeUnroutable and the caller treats the request as unroutable.

type Outcome

type Outcome byte

Outcome describes why Switch chose (or failed to choose) an upstream.

const (
	// OutcomeMatch means a rule matched the request.
	OutcomeMatch Outcome = iota
	// OutcomeDefault means the request fell through to the default upstream.
	OutcomeDefault
	// OutcomeSystem means a no-namespace request went to the system upstream.
	OutcomeSystem
	// OutcomeUnroutable means no upstream was selected (result "").
	OutcomeUnroutable
)

func (Outcome) String

func (o Outcome) String() string

String returns the metric label value for the outcome.

type Reflector

type Reflector interface {
	Namespace(string, []byte) string
}

Reflector extracts the Temporal namespace from a request. Namespace receives the full method and the raw bytes of the first request message and returns the namespace, or "" when it cannot determine one.

type Reporter

type Reporter struct {
	// contains filtered or unexported fields
}

Reporter records router telemetry to Prometheus: routing decisions and the forwarding failures the router itself originates. It pre-resolves a counter for every meaningful (upstream, outcome) and (upstream, reason) combination so the emit path is a lock-free map read; an unexpected label combination falls back to CounterVec.WithLabelValues. A Reporter is safe for concurrent use.

func NewReporter

func NewReporter(f *metrics.Factory, upstreams []string) *Reporter

NewReporter builds the Prometheus-backed Reporter, registering its collectors with the factory's registry and pre-resolving the meaningful label combinations so every series starts at zero. upstreams is the configured upstream name list.

func (*Reporter) Decision

func (r *Reporter) Decision(upstream string, outcome Outcome)

Decision increments the decision counter for the chosen upstream and outcome.

func (*Reporter) ForwardingError

func (r *Reporter) ForwardingError(upstream, reason string)

ForwardingError increments the forwarding-error counter for the upstream and reason.

type RouterParams

type RouterParams struct {
	fx.In

	Config    *config.Config
	Extractor *protoutil.Extractor
	Mux       *Mux
	Pool      *connect.Pool
	Reporter  *Reporter
}

RouterParams collects the fx-provided dependencies needed to build the forwarding stream handler.

type Rule

type Rule struct {
	// contains filtered or unexported fields
}

Rule routes every request it matches to a named upstream. A request matches when the rule's namespace matcher accepts the request namespace and, for every constrained metadata key, at least one of the request's values for that key is accepted. Metadata keys are compared as stored, so the rule builder is responsible for canonicalizing them (gRPC lowercases metadata keys). Construct rules within this package.

type Target

type Target struct {
	Upstream string
	Conn     *grpc.ClientConn
}

Target is the routing result returned by Director.Resolve on success: the chosen upstream's name (always non-empty) and the connection to forward the stream over. On a non-nil error the Target is unused and callers must ignore its fields.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL