Documentation
¶
Overview ¶
Package rewrite owns per-host HTTP header rewrite rules: the rule DTO, the ordered active rule set, and the request/response header mutators. It is a self-contained leaf (stdlib + the uuid generator, no Culvert coupling) extracted from the flat package main per ADR-0002.
Index ¶
- func FingerprintRules(rules []Rule) string
- func NewStableID() string
- type Rewriter
- func (rw *Rewriter) Add(rule Rule) Rule
- func (rw *Rewriter) ApplyRequest(host string, h http.Header)
- func (rw *Rewriter) ApplyResponse(host string, resp *http.Response)
- func (rw *Rewriter) List() []Rule
- func (rw *Rewriter) RemoveByID(id int) bool
- func (rw *Rewriter) RemoveByStableID(stableID string) bool
- func (rw *Rewriter) SetRules(rules []Rule) (backfilled int)
- func (rw *Rewriter) Snapshot() func()
- func (rw *Rewriter) StateSnapshot() (rules []Rule, revision string)
- type Rule
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FingerprintRules ¶ added in v1.0.218
FingerprintRules is the canonical content fingerprint of an ORDERED rewrite rule set (§26): it covers stable identity, position, host, and every request/response header operation, with map keys sorted deterministically so map iteration order can never make the revision nondeterministic. The process-local integer ID is deliberately excluded — it changes across restarts without any semantic change.
func NewStableID ¶ added in v1.0.218
func NewStableID() string
NewStableID mints a durable rewrite-rule identity (server-owned; the interactive create path calls this so a client-supplied value is never trusted).
Types ¶
type Rewriter ¶
type Rewriter struct {
// contains filtered or unexported fields
}
Rewriter holds the ordered list of active rewrite rules and applies them.
func NewRewriter ¶
func NewRewriter() *Rewriter
NewRewriter returns a Rewriter with ID assignment starting at 1.
func (*Rewriter) Add ¶
Add appends a rule and returns it with the assigned identities. The interactive trust boundary (§22): any CALLER-supplied StableID is ignored — the server owns identity generation on create.
func (*Rewriter) ApplyRequest ¶
ApplyRequest mutates h in-place for every matching rule.
func (*Rewriter) ApplyResponse ¶
ApplyResponse mutates resp.Header in-place for every matching rule.
func (*Rewriter) RemoveByID ¶
RemoveByID deletes the rule with the given process-local integer ID (legacy-compat addressing). Returns false if not found.
func (*Rewriter) RemoveByStableID ¶ added in v1.0.218
RemoveByStableID deletes the rule with the given durable identity (the v2 management addressing). Returns false if not found.
func (*Rewriter) SetRules ¶
SetRules replaces the full rule set, PRESERVING LIST ORDER (order is evaluation semantics, §23) and every non-empty StableID verbatim (§21 — restart/rollback/snapshot must never re-identify a known rule). Legacy rules without a StableID are backfilled exactly once here; the returned count tells the caller whether a one-time migration happened so it can make the backfilled identities durable through the real persistence owner. Process-local integer IDs are reassigned as before (compatibility only).
DEFENSIVE dedupe: the validated doors (import / rollback / snapshot candidate checks) reject duplicate StableIDs before any apply reaches here; if a duplicate still arrives (hand-edited file that bypassed a door), the FIRST occurrence keeps the identity and later duplicates are regenerated — counted in the return so the caller persists and logs, never silently.
func (*Rewriter) Snapshot ¶
func (rw *Rewriter) Snapshot() func()
Snapshot captures the current rules and ID counter and returns a closure that restores them, under the mutex on both ends. Production code never calls this; it exists so package main's startup-slice test isolation helper can save and restore the package-global rewriter without reaching across the package boundary into the unexported fields (ADR-0002 extraction).
func (*Rewriter) StateSnapshot ¶ added in v1.0.218
StateSnapshot returns the ordered rules AND the content-derived revision that describes exactly them, from ONE lock hold (v2 coherent-read contract): rows from one state must never pair with another state's fence.
type Rule ¶
type Rule struct {
// ID is assigned automatically when the rule is added at runtime.
//
// COMPATIBILITY/PROCESS-LOCAL ONLY (2D-C §19): SetRules reassigns
// sequential IDs on every load, so this integer is NOT durable object
// identity — it exists for legacy clients that address rules by it within
// one process lifetime. Management identity is StableID.
ID int `json:"id"`
// StableID is the server-owned DURABLE rule identity (2D-C §20): a UUID
// assigned once — at interactive Add, or backfilled exactly once when a
// legacy persisted/imported rule without one first passes through
// SetRules — and preserved verbatim across restart, export/import,
// config-version rollback, and CP→DP snapshot sync (the persistence owner
// is AdminSettings, which snapshots rules WITH their StableIDs). The v2
// management surface addresses rules ONLY by this. Never derived from
// array position or rule contents: two identical rules are two different
// objects. yaml:"-" — YAML-authored config rules receive their identity at
// first load and it becomes durable through the settings owner.
StableID string `yaml:"-" json:"stableId,omitempty"`
// Host is an exact hostname or wildcard pattern (*.example.com).
// Empty string matches every request.
Host string `yaml:"host" json:"host"`
// Request header operations — applied before forwarding to upstream.
ReqSet map[string]string `yaml:"req_set" json:"req_set,omitempty"` // set / overwrite
ReqAdd map[string]string `yaml:"req_add" json:"req_add,omitempty"` // append
ReqRemove []string `yaml:"req_remove" json:"req_remove,omitempty"` // delete
// Response header operations — applied before returning to client.
RespSet map[string]string `yaml:"resp_set" json:"resp_set,omitempty"`
RespAdd map[string]string `yaml:"resp_add" json:"resp_add,omitempty"`
RespRemove []string `yaml:"resp_remove" json:"resp_remove,omitempty"`
}
Rule defines header mutations applied to requests and/or responses whose destination host matches the given pattern.
Example (config.yaml):
rewrite:
- host: "*.internal.corp"
req_set:
X-Forwarded-By: "Culvert"
resp_remove:
- Server
- host: "" # empty = match all hosts
resp_set:
Strict-Transport-Security: "max-age=31536000"