idempotency

package
v1.5.6 Latest Latest
Warning

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

Go to latest
Published: Jun 29, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package idempotency implements the HTTP Idempotency-Key request pattern for Celeris.

New returns a middleware that deduplicates retried writes. When a request carries the header named by Config.KeyHeader (default "Idempotency-Key"), the middleware acquires an atomic lock via KVStore, runs the handler once, persists the response, and replays it on subsequent requests with the same key. Concurrent duplicates return 409 Conflict (overridable via Config.OnConflict). Requests without the key header pass through unchanged.

Key types: Config controls all behaviour; KVStore is the store interface (store.KV + store.SetNXer); NewMemoryStore provides a sharded in-memory default suitable for single-instance deployments. For multi-instance deployments, supply a Redis-backed store and namespace it with store.Prefixed. Set Config.BodyHash to detect key reuse with different payloads (returns 422 on mismatch). ErrStoreMissingSetNX is returned by New when a provided store lacks atomic SetNX support.

Documentation

Full guides and examples: https://goceleris.dev/docs/middleware-traffic

Package idempotency implements the HTTP Idempotency-Key pattern.

When a request carries an Idempotency-Key header, the middleware attempts to acquire an atomic lock on the key via store.SetNXer. The first request to acquire the lock runs the handler, persists the response under the same key, and releases the lock. Subsequent requests with the same key replay the stored response.

Concurrent duplicates that arrive while the original is still in-flight return 409 Conflict (configurable via Config.OnConflict). Crashed handlers leak a lock entry; the lock expires after Config.LockTimeout so the next request can retry.

When Config.BodyHash is enabled, the request body hash is stored alongside the response; mismatches on replay return 422 Unprocessable Entity to catch clients that reuse a key with different payloads.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrStoreMissingSetNX = errors.New("idempotency: store must implement store.SetNXer")

ErrStoreMissingSetNX is returned by New when Config.Store is set but does not satisfy KVStore.

Functions

func New

func New(config ...Config) celeris.HandlerFunc

New returns an idempotency middleware.

Types

type Config

type Config struct {
	// Store persists completed responses and lock entries. Default:
	// [NewMemoryStore]. Must implement [store.KV] + [store.SetNXer].
	Store KVStore

	// KeyHeader is the request header carrying the client-supplied
	// idempotency key. Default: "Idempotency-Key".
	KeyHeader string

	// TTL is the lifetime of a completed response entry. Default: 24h.
	TTL time.Duration

	// LockTimeout is the maximum duration a lock is held before it
	// expires (recovers after a crashed handler). Default: 30s.
	LockTimeout time.Duration

	// Methods lists HTTP methods the middleware applies to. Default:
	// POST, PUT, PATCH, DELETE. Methods outside this list pass through.
	Methods []string

	// OnConflict runs when a duplicate request arrives while the
	// original is still in-flight (lock held, no completed entry yet).
	// Default: respond with 409 Conflict.
	OnConflict func(*celeris.Context) error

	// MaxKeyLength is the upper bound for the key header value.
	// Default: 255.
	MaxKeyLength int

	// BodyHash, when true, hashes the request body (up to MaxBodyBytes)
	// and compares it to the stored hash on replay. Mismatches return
	// 422 Unprocessable Entity. Default: false (hash checking disabled).
	BodyHash bool

	// MaxBodyBytes caps the bytes hashed for BodyHash and the bytes
	// stored in a completed response. Default: 1 MiB.
	MaxBodyBytes int

	// Skip defines a function to skip this middleware for certain
	// requests.
	Skip func(*celeris.Context) bool

	// SkipPaths lists paths to skip (exact match).
	SkipPaths []string

	// CleanupContext stops the MemoryStore cleanup goroutine (no effect
	// on Redis/Postgres stores).
	CleanupContext context.Context
}

Config defines the idempotency middleware configuration.

Example

ExampleConfig — minimum useful configuration. Clients send `Idempotency-Key: <uuid>` on POST/PUT/DELETE; retries within the TTL replay the cached response and concurrent duplicates while the original is still in-flight return 409 Conflict. `store.MemoryKV` satisfies idempotency.KVStore (KV + SetNXer) out of the box.

package main

import (
	"time"

	"github.com/goceleris/celeris/middleware/idempotency"
	"github.com/goceleris/celeris/middleware/store"
)

func main() {
	_ = idempotency.Config{
		Store: store.NewMemoryKV(),
		TTL:   5 * time.Minute,
	}
}

type KVStore

type KVStore interface {
	store.KV
	store.SetNXer
}

KVStore combines store.KV with store.SetNXer — idempotency requires both. The in-memory default satisfies this out of the box. Backends without SetNX cannot be used as an idempotency store.

func NewMemoryStore

func NewMemoryStore(config ...MemoryStoreConfig) KVStore

NewMemoryStore returns an in-memory idempotency store. It wraps store.MemoryKV, which implements both store.KV and store.SetNXer — the minimum surface New needs.

type MemoryStoreConfig

type MemoryStoreConfig = store.MemoryKVConfig

MemoryStoreConfig is a type alias for store.MemoryKVConfig provided for symmetry with other middleware packages.

Jump to

Keyboard shortcuts

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