vanguard

package
v0.0.0-...-4dd1f93 Latest Latest
Warning

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

Go to latest
Published: Apr 4, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package vanguard provides a unified server that serves gRPC, Connect, gRPC-Web, and REST protocols on a single port via Vanguard transcoder with h2c support.

Overview

This package implements the core v5.0 server that composes multiple protocol handlers into a single HTTP endpoint. It uses connectrpc.com/vanguard to transcode between gRPC, Connect, gRPC-Web, and REST (via google.api.http annotations) without any code generation.

Services are auto-discovered from the DI container:

  • Connect services implement connect.Registrar and are resolved via di.ResolveAll[connect.Registrar].
  • gRPC services are bridged through the gRPC server's raw *grpc.Server via vanguardgrpc.NewTranscoder.

Quick Start

Use the module to register the Vanguard server with your application:

app := gaz.New()
app.Use(grpc.NewModule())      // gRPC services (skip-listener mode)
app.Use(vanguard.NewModule())  // Vanguard unified server

Health Endpoints

When a health.Manager is present in the DI container, the server automatically mounts health endpoints on the unknown handler. Paths are configurable via health.Config (defaults shown):

  • /ready — readiness probe
  • /live — liveness probe
  • /startup — startup probe

Reflection

gRPC reflection (v1 and v1alpha) is enabled by default for grpcurl compatibility. Reflection handlers are registered as Connect-style services in the Vanguard transcoder.

Configuration

Configuration uses the "server" namespace:

server:
  port: 8080
  read_header_timeout: 5s
  idle_timeout: 120s
  reflection: true
  health_enabled: true

Index

Constants

View Source
const (
	// PriorityCORS is the priority for the CORS middleware (runs first).
	PriorityCORS = 0
	// PriorityOTEL is the priority for the OTEL middleware (runs after CORS).
	PriorityOTEL = 100
)

Transport middleware priority constants. Lower values run first (outermost in the handler chain).

View Source
const DefaultCORSMaxAge = 86400

DefaultCORSMaxAge is the default max age for preflight request caching (24 hours in seconds).

View Source
const DefaultIdleTimeout = 120 * time.Second

DefaultIdleTimeout is the default idle timeout for keep-alive connections.

View Source
const DefaultPort = 8080

DefaultPort is the default port for the Vanguard server.

View Source
const DefaultReadHeaderTimeout = 5 * time.Second

DefaultReadHeaderTimeout is the default read header timeout. This protects against slowloris attacks.

Variables

This section is empty.

Functions

func NewModule

func NewModule() gaz.Module

NewModule creates a Vanguard module. Returns a gaz.Module that registers Vanguard server components.

Components registered:

  • vanguard.Config (loaded from flags/config)
  • *vanguard.CORSMiddleware (transport middleware, always registered)
  • *vanguard.OTELMiddleware (transport middleware, only if TracerProvider registered)
  • *vanguard.OTELConnectBundle (connect interceptor bundle, only if TracerProvider registered)
  • *connect.LoggingBundle (connect logging interceptor, always registered)
  • *connect.RecoveryBundle (connect panic recovery interceptor, always registered)
  • *connect.ValidationBundle (connect protovalidate interceptor, always registered)
  • *connect.AuthBundle (connect auth interceptor, only if AuthFunc registered)
  • *connect.RateLimitBundle (connect rate limit interceptor, uses AlwaysPassLimiter unless Limiter registered)
  • *vanguard.Server (eager, starts on app start)

The module depends on grpc.NewModule() being registered first, as it resolves *grpc.Server from the DI container to bridge gRPC services into the Vanguard transcoder.

Example:

app := gaz.New()
app.Use(grpc.NewModule())      // Must come first
app.Use(vanguard.NewModule())  // Vanguard unified server

Types

type CORSConfig

type CORSConfig struct {
	// AllowedOrigins is a list of allowed origins.
	// Use ["*"] to allow all origins (dev mode only, not with credentials).
	AllowedOrigins []string `json:"allowed_origins" yaml:"allowed_origins" mapstructure:"allowed_origins"`

	// AllowedMethods is a list of allowed HTTP methods.
	AllowedMethods []string `json:"allowed_methods" yaml:"allowed_methods" mapstructure:"allowed_methods"`

	// AllowedHeaders is a list of allowed request headers.
	// Use ["*"] to allow all headers (dev mode only).
	AllowedHeaders []string `json:"allowed_headers" yaml:"allowed_headers" mapstructure:"allowed_headers"`

	// ExposedHeaders is a list of headers exposed to the browser.
	ExposedHeaders []string `json:"exposed_headers" yaml:"exposed_headers" mapstructure:"exposed_headers"`

	// AllowCredentials indicates whether credentials (cookies, auth headers) are allowed.
	// Cannot be used with AllowedOrigins ["*"].
	AllowCredentials bool `json:"allow_credentials" yaml:"allow_credentials" mapstructure:"allow_credentials"`

	// MaxAge is the maximum age (in seconds) for preflight request caching.
	MaxAge int `json:"max_age" yaml:"max_age" mapstructure:"max_age"`
}

CORSConfig holds CORS configuration for the Vanguard server.

func DefaultCORSConfig

func DefaultCORSConfig(devMode bool) CORSConfig

DefaultCORSConfig returns a CORSConfig with appropriate defaults. In dev mode, CORS is wide-open for convenience. In prod mode, origins must be explicitly configured.

type CORSMiddleware

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

CORSMiddleware implements TransportMiddleware for CORS handling. In dev mode, it allows all origins. In production, it applies configured CORS restrictions.

func NewCORSMiddleware

func NewCORSMiddleware(cfg CORSConfig, devMode bool) *CORSMiddleware

NewCORSMiddleware creates a new CORS transport middleware. In dev mode, all origins are allowed. In production, the configured CORSConfig origins, methods, and headers are enforced.

func (*CORSMiddleware) Name

func (m *CORSMiddleware) Name() string

Name returns the middleware identifier.

func (*CORSMiddleware) Priority

func (m *CORSMiddleware) Priority() int

Priority returns the CORS priority (outermost handler).

func (*CORSMiddleware) Wrap

func (m *CORSMiddleware) Wrap(next http.Handler) http.Handler

Wrap applies CORS handling to the given handler.

type Config

type Config struct {
	// Port is the TCP port the Vanguard server listens on.
	// Defaults to 8080 if not set.
	Port int `json:"port" yaml:"port" mapstructure:"port" gaz:"port"`

	// ReadTimeout is the maximum duration for reading the entire request.
	// Zero means no timeout, which is required for streaming RPCs.
	// Defaults to 0 (streaming-safe).
	ReadTimeout time.Duration `json:"read_timeout" yaml:"read_timeout" mapstructure:"read_timeout" gaz:"read_timeout"`

	// WriteTimeout is the maximum duration before timing out writes of the response.
	// Zero means no timeout, which is required for streaming RPCs.
	// Defaults to 0 (streaming-safe).
	WriteTimeout time.Duration `json:"write_timeout" yaml:"write_timeout" mapstructure:"write_timeout" gaz:"write_timeout"`

	// IdleTimeout is the maximum duration an idle keep-alive connection will remain open.
	// Defaults to 120 seconds.
	IdleTimeout time.Duration `json:"idle_timeout" yaml:"idle_timeout" mapstructure:"idle_timeout" gaz:"idle_timeout"`

	// ReadHeaderTimeout is the maximum duration for reading request headers.
	// This protects against slowloris attacks.
	// Defaults to 5 seconds.
	ReadHeaderTimeout time.Duration `json:"read_header_timeout" yaml:"read_header_timeout" mapstructure:"read_header_timeout" gaz:"read_header_timeout"`

	// Reflection enables gRPC reflection via Connect handlers (v1 and v1alpha).
	// When enabled, tools like grpcurl can introspect available services.
	// Defaults to true.
	Reflection bool `json:"reflection" yaml:"reflection" mapstructure:"reflection" gaz:"reflection"`

	// HealthEnabled enables automatic health endpoint mounting.
	// When enabled and health.Manager is present, health endpoints are mounted
	// on the unknown handler using paths from health.Config.
	// Defaults to true.
	HealthEnabled bool `json:"health_enabled" yaml:"health_enabled" mapstructure:"health_enabled" gaz:"health_enabled"`

	// DevMode enables development mode for verbose error messages.
	// Defaults to false.
	DevMode bool `json:"dev_mode" yaml:"dev_mode" mapstructure:"dev_mode" gaz:"dev_mode"`

	// AllowZeroWriteTimeout explicitly opts in to zero write timeout.
	// When false (default), WriteTimeout=0 is rejected by Validate as a Slowloris risk.
	// Set to true only when streaming RPCs require no write timeout.
	AllowZeroWriteTimeout bool `` /* 134-byte string literal not displayed */

	// CORS contains CORS configuration for the Vanguard server.
	CORS CORSConfig `json:"cors" yaml:"cors" mapstructure:"cors" gaz:"cors"`
}

Config holds configuration for the Vanguard server.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a Config with safe defaults. ReadTimeout and WriteTimeout are intentionally zero for streaming safety.

func (*Config) Flags

func (c *Config) Flags(fs *pflag.FlagSet)

Flags registers the config flags. ReadTimeout and WriteTimeout are not exposed as flags because zero is the correct default for streaming RPCs. Users should only change them via config file if they understand the streaming implications.

func (*Config) Namespace

func (c *Config) Namespace() string

Namespace returns the config namespace. The Vanguard server uses "server" as its namespace, so flags are prefixed with server-.

func (*Config) SetDefaults

func (c *Config) SetDefaults()

SetDefaults applies default values to zero-value fields. ReadTimeout and WriteTimeout are NOT defaulted because zero is intentional for streaming safety. Only Port, ReadHeaderTimeout, and IdleTimeout are filled. Implements the config.Defaulter interface.

func (*Config) Validate

func (c *Config) Validate() error

Validate checks that the configuration is valid. ReadTimeout=0 and WriteTimeout=0 are explicitly accepted as valid because streaming RPCs require no timeout on reads and writes. Implements the config.Validator interface.

type OTELConnectBundle

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

OTELConnectBundle implements connect.InterceptorBundle for OpenTelemetry Connect RPC tracing. It provides otelconnect interceptors when a TracerProvider is available.

func NewOTELConnectBundle

func NewOTELConnectBundle(tp *sdktrace.TracerProvider, logger *slog.Logger) *OTELConnectBundle

NewOTELConnectBundle creates a new OTEL Connect interceptor bundle.

func (*OTELConnectBundle) Interceptors

func (b *OTELConnectBundle) Interceptors() []connect.Interceptor

Interceptors returns the otelconnect interceptor. Health check procedures are filtered from traces. If interceptor creation fails, a warning is logged and an empty slice is returned.

func (*OTELConnectBundle) Name

func (b *OTELConnectBundle) Name() string

Name returns the bundle identifier.

func (*OTELConnectBundle) Priority

func (b *OTELConnectBundle) Priority() int

Priority returns the OTEL Connect priority (before validation, after auth).

type OTELMiddleware

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

OTELMiddleware implements TransportMiddleware for OpenTelemetry HTTP tracing. It wraps the handler with otelhttp instrumentation, filtering out health and reflection endpoints. Health paths are read from health.Config so they stay in sync with the actual health endpoint paths.

func NewOTELMiddleware

func NewOTELMiddleware(tp *sdktrace.TracerProvider, healthCfg health.Config) *OTELMiddleware

NewOTELMiddleware creates a new OTEL transport middleware with the given TracerProvider and health configuration. Health check paths from healthCfg are excluded from traces to reduce noise.

func (*OTELMiddleware) Name

func (m *OTELMiddleware) Name() string

Name returns the middleware identifier.

func (*OTELMiddleware) Priority

func (m *OTELMiddleware) Priority() int

Priority returns the OTEL priority (after CORS).

func (*OTELMiddleware) Wrap

func (m *OTELMiddleware) Wrap(next http.Handler) http.Handler

Wrap applies OpenTelemetry HTTP instrumentation to the given handler. Health endpoints (configurable via health.Config) and gRPC reflection endpoints are filtered from traces to reduce noise.

type Server

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

Server is a unified server that composes gRPC, Connect, gRPC-Web, and REST protocols on a single port via Vanguard transcoder with h2c support. It implements di.Starter and di.Stopper for lifecycle management.

func NewServer

func NewServer(cfg Config, logger *slog.Logger, container *di.Container, grpcServer *grpc.Server) *Server

NewServer creates a new Vanguard server with the given configuration. The server is not started until OnStart is called.

Parameters:

  • cfg: Server configuration (port, timeouts, reflection, health)
  • logger: Logger for server events (falls back to slog.Default)
  • container: DI container for service discovery
  • grpcServer: The raw *grpc.Server from the gRPC module (for vanguardgrpc bridge)

func (*Server) OnStart

func (s *Server) OnStart(ctx context.Context) error

OnStart starts the Vanguard server. It discovers Connect services, bridges gRPC services, registers reflection and health handlers, builds the Vanguard transcoder, and starts serving over h2c on the configured port. Implements di.Starter.

func (*Server) OnStop

func (s *Server) OnStop(ctx context.Context) error

OnStop gracefully shuts down the Vanguard server. It waits for active connections to drain or forces shutdown on context timeout. Implements di.Stopper.

func (*Server) SetUnknownHandler

func (s *Server) SetUnknownHandler(h http.Handler)

SetUnknownHandler sets a user-defined handler for non-RPC HTTP routes. Must be called before OnStart.

type TransportMiddleware

type TransportMiddleware interface {
	// Name returns a unique identifier for logging and debugging.
	Name() string

	// Priority determines the order in the middleware chain.
	// Lower values wrap outermost (run first on request, last on response).
	Priority() int

	// Wrap applies the middleware to the given handler.
	Wrap(http.Handler) http.Handler
}

TransportMiddleware wraps an http.Handler with cross-cutting HTTP concerns. Implementations are automatically discovered from the DI container and applied in priority order around the Vanguard handler.

Jump to

Keyboard shortcuts

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