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:
- Codec returns a hybrid google.golang.org/grpc/encoding.CodecV2 that passes relayed frames through as raw bytes and delegates every other message to the standard proto codec, so locally registered services (such as health) keep working alongside the relay.
- Handler returns a google.golang.org/grpc.StreamHandler for use as a google.golang.org/grpc.UnknownServiceHandler. It opens a same-method stream on the upstream connection and pumps raw frames in both directions, propagating header, trailer, and status verbatim.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 )
type Reflector ¶
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 ¶
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 ¶
Decision increments the decision counter for the chosen upstream and outcome.
func (*Reporter) ForwardingError ¶
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.