api

package
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Sep 14, 2026 License: Apache-2.0 Imports: 54 Imported by: 0

Documentation

Overview

Package api is a platform module: a gRPC server and a REST gateway in front of it, with the interceptors every service needs and the OpenAPI description of the API.

The project writes proto files and handlers and registers its services from wireDomain; the module owns the servers, the interceptor chain, the gateway options, health, metrics, CORS and the documentation page.

Index

Constants

View Source
const IdempotencyKeyHeader = "Idempotency-Key"

IdempotencyKeyHeader is the header a client sends to make a call safe to repeat.

Variables

View Source
var DefaultTrustedProxies = []netip.Prefix{
	netip.MustParsePrefix("127.0.0.0/8"),
	netip.MustParsePrefix("10.0.0.0/8"),
	netip.MustParsePrefix("172.16.0.0/12"),
	netip.MustParsePrefix("192.168.0.0/16"),
	netip.MustParsePrefix("::1/128"),
	netip.MustParsePrefix("fc00::/7"),
}

DefaultTrustedProxies are loopback and the private networks.

Functions

func AddStreamInterceptor

func AddStreamInterceptor(app *platform.App, i grpc.StreamServerInterceptor)

AddStreamInterceptor adds a project stream interceptor.

func AddUnaryInterceptor

func AddUnaryInterceptor(app *platform.App, i grpc.UnaryServerInterceptor)

AddUnaryInterceptor adds a project interceptor, such as authentication. Project interceptors run after recovery, metrics and logging and before request validation.

func Authorize added in v0.3.0

func Authorize(app *platform.App, unary grpc.UnaryServerInterceptor, stream grpc.StreamServerInterceptor)

Authorize adds an access check. It runs after every project interceptor — so the caller's identity is already in the context — and before request validation. The rbac module registers itself here.

func BodyTooLarge added in v0.4.0

func BodyTooLarge(r *http.Request) bool

BodyTooLarge reports whether the request body is over API_MAX_RECV_SIZE: declared larger, or found larger while it was read. A project middleware that reads the body itself, such as a signature check, answers 413 when it is.

func ClientIP added in v0.3.0

func ClientIP(ctx context.Context) string

ClientIP returns the address of the client. Behind a proxy it is the last address of X-Forwarded-For — the one the proxy itself saw, which the client cannot forge — and the connection address otherwise. The gateway passes it on to gRPC handlers.

func HTTPErrorHandler added in v0.4.0

func HTTPErrorHandler(app *platform.App, h runtime.ErrorHandlerFunc)

HTTPErrorHandler replaces how the REST side writes an error, for an API whose contract has its own error body. It receives every error of the gateway: from the handlers, from request decoding and routing, and a body over the limit as a *runtime.HTTPStatusError with 413. A later call replaces an earlier one.

func HandleHTTP

func HandleHTTP(app *platform.App, pattern string, h http.Handler)

HandleHTTP serves a plain HTTP route next to the gateway: webhooks, file downloads, anything that is not a gRPC call.

func IdempotencyUser added in v0.3.0

func IdempotencyUser(app *platform.App, user func(ctx context.Context) string)

IdempotencyUser scopes idempotency keys to the caller: two users may use the same key. The project's authentication supplies the user.

func Methods added in v0.3.0

func Methods(app *platform.App) []string

Methods returns the full names of every registered gRPC method, such as /shop.v1.OrdersService/CreateOrder. It is filled when the module starts.

func PublicMethods added in v0.3.0

func PublicMethods(app *platform.App, public func(method string) bool)

PublicMethods tells the API which methods are public, for their separate rate limit. The rbac module sets it from its policy.

func Register

func Register(app *platform.App, s Service)

Register adds a gRPC service and its gateway.

func RequestID added in v0.3.0

func RequestID(ctx context.Context) string

RequestID returns the id of the request: the X-Request-ID the client sent, or one generated for it. It works for HTTP handlers and for gRPC handlers behind the gateway.

func RequireIdempotency added in v0.3.0

func RequireIdempotency(app *platform.App, methods ...string)

RequireIdempotency makes the methods refuse a call without an Idempotency-Key header, as taply does for every method that creates something.

func UseHTTP

func UseHTTP(app *platform.App, mw func(http.Handler) http.Handler)

UseHTTP wraps the whole REST side in a middleware.

Types

type Config

type Config struct {
	GRPCAddr    string   // gRPC listen address
	HTTPAddr    string   // REST gateway listen address
	MaxRecvSize int      // largest request message, bytes
	MaxSendSize int      // largest response message, bytes
	CORSOrigins []string // origins allowed to call the REST API from a browser; "*" allows any
	Docs        bool     // serve /openapi.yaml and the /docs page
	Reflection  bool     // gRPC server reflection, for grpcurl and similar tools

	// RateLimit per client address; zero rates turn it off.
	RateLimit RateLimit

	// Idempotency: how long a finished call is remembered, and how long a running one
	// holds its key before a repeat may take over.
	IdempotencyRetention time.Duration
	IdempotencyLock      time.Duration

	AccessLog       bool // log every HTTP request, as taply does
	LogBodies       bool // put the bodies of failed requests into the access log
	SecurityHeaders bool // taply's security response headers

	// TrustedProxies are the networks whose X-Forwarded-For is believed. A request from
	// anywhere else is identified by its connection address, so a client reaching the
	// service directly cannot pose as another address. Empty means the private networks
	// and loopback, where a reverse proxy normally runs.
	TrustedProxies []netip.Prefix
}

Config holds module settings. Load fills it from the environment; the platform generator writes the Load call into the project's config.gen.go.

func Load

func Load(l *confx.Loader) Config

Load reads the module settings from environment variables.

type IdempotencyRecord added in v0.3.0

type IdempotencyRecord struct {
	ID          int64
	User        string
	Method      string
	Key         string
	Fingerprint string
	Status      string
	Code        int32
	Message     string
	Response    []byte
	ExpiresAt   time.Time
}

IdempotencyRecord is one remembered call.

type IdempotencyStore added in v0.3.0

type IdempotencyStore interface {
	// Begin inserts an in_progress record, or returns the existing one with created false.
	Begin(ctx context.Context, rec IdempotencyRecord) (IdempotencyRecord, bool, error)
	// Retake moves a retry record, or an in_progress one whose lock expired, back to
	// in_progress. It reports false when another call took it first.
	Retake(ctx context.Context, id int64, lockUntil time.Time) (bool, error)
	// Finish stores the outcome.
	Finish(ctx context.Context, rec IdempotencyRecord) error
	// DeleteExpired removes records past their expiry.
	DeleteExpired(ctx context.Context, now time.Time) (int, error)
}

IdempotencyStore keeps the records. The postgres store is used when the postgres module is enabled; tests use the memory one.

func NewPostgresIdempotencyStore added in v0.3.0

func NewPostgresIdempotencyStore(ctx context.Context, pool *pgxpool.Pool) (IdempotencyStore, error)

NewPostgresIdempotencyStore creates the table if needed and returns the store the module uses with the postgres module.

type MemoryIdempotencyStore added in v0.3.0

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

MemoryIdempotencyStore keeps records in memory. It does not survive a restart and is not shared between instances: it is for tests.

func NewMemoryIdempotencyStore added in v0.3.0

func NewMemoryIdempotencyStore() *MemoryIdempotencyStore

NewMemoryIdempotencyStore creates an empty store.

func (*MemoryIdempotencyStore) Begin added in v0.3.0

func (*MemoryIdempotencyStore) DeleteExpired added in v0.3.0

func (s *MemoryIdempotencyStore) DeleteExpired(_ context.Context, now time.Time) (int, error)

func (*MemoryIdempotencyStore) Finish added in v0.3.0

func (*MemoryIdempotencyStore) Retake added in v0.3.0

func (s *MemoryIdempotencyStore) Retake(_ context.Context, id int64, lockUntil time.Time) (bool, error)

type Module

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

Module implements platform.Module.

func New

func New(cfg Config, opts ...Option) *Module

New creates the module from ready settings.

func (*Module) GRPCAddr

func (m *Module) GRPCAddr() string

GRPCAddr is the address the gRPC server listens on.

func (*Module) HTTPAddr

func (m *Module) HTTPAddr() string

HTTPAddr is the address the REST gateway listens on.

func (*Module) Health

func (m *Module) Health(context.Context) error

Health reports whether both servers are serving.

func (*Module) Init

func (m *Module) Init(_ context.Context, app *platform.App) error

Init puts the registry into the container, so wireDomain can register services.

func (*Module) Name

func (m *Module) Name() string

func (*Module) Start

func (m *Module) Start(ctx context.Context) error

Start builds the servers from what the project registered and starts serving.

func (*Module) Stop

func (m *Module) Stop(ctx context.Context) error

Stop stops taking requests and lets the running ones finish within the shutdown timeout; what is still running after it is cut off.

type Option

type Option func(*Module)

Option configures the module.

func WithIdempotencyStore added in v0.3.0

func WithIdempotencyStore(store IdempotencyStore) Option

WithIdempotencyStore sets where idempotency records are kept. Without it the module uses the database of the postgres module; tests pass a memory store.

func WithOpenAPI

func WithOpenAPI(fsys fs.FS) Option

WithOpenAPI gives the module the generated OpenAPI description: a directory holding openapi.yaml. The generated wiring passes the embedded api/openapi directory.

type RateLimit added in v0.3.0

type RateLimit struct {
	RPS, PublicRPS     float64
	Burst, PublicBurst int
}

RateLimit is a token bucket per client address, with separate limits for public methods, as in taply.

type Registry

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

Registry holds what the project adds to the API. It lives in the container and is filled from wireDomain, before the module starts.

type RetryableError added in v0.3.0

type RetryableError interface{ IsRetryable() bool }

RetryableError marks an error after which a repeated call must run again instead of getting the cached failure: a timeout of an external system, a lost connection.

type Service

type Service struct {
	GRPC    func(*grpc.Server)
	Gateway func(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error
}

Service is a gRPC service of the project and, optionally, its REST gateway. Both functions are generated: RegisterXServer from protoc-gen-go-grpc and RegisterXHandler from protoc-gen-grpc-gateway.

api.Register(app, api.Service{
	GRPC:    func(s *grpc.Server) { ordersv1.RegisterOrdersServiceServer(s, handler) },
	Gateway: ordersv1.RegisterOrdersServiceHandler,
})

Jump to

Keyboard shortcuts

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