bodytap

package
v0.74.7 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause, AGPL-3.0 Imports: 9 Imported by: 0

Documentation

Overview

Package bodytap owns the framework-side body capture used by the middleware chain. Request capture buffers up to N bytes of the request body for middleware inspection while replaying the original stream to the upstream. Response capture tees up to N bytes off the streaming response while every byte continues to flow to the client untouched.

The package is the single owner of body access — middlewares never read req.Body or hijack the response writer. All inspection happens against the buffer surfaced by the tap, so streaming remains transparent to the client even when middlewares need access to the payload.

Index

Constants

View Source
const (
	BypassUpgradeHeader    = "upgrade_header"
	BypassConnectionUpgrd  = "connection_upgrade"
	BypassContentType      = "content_type_not_allowed"
	BypassBudget           = "capture_budget_exhausted"
	BypassNoConfig         = "no_capture_config"
	BypassNoMiddlewares    = "no_middlewares"
	BypassCapZero          = "cap_zero"
	BypassContentLengthCap = "content_length_over_cap"
)

Request bypass reasons emitted as the `mw.capture.bypass_reason` metadata key by the chain when a request body is not surfaced.

View Source
const DefaultCaptureBudgetBytes int64 = 256 << 20

DefaultCaptureBudgetBytes is the default global capture-budget size.

View Source
const MaxRoutingScanBytes int64 = 32 << 20

MaxRoutingScanBytes bounds how far ScanRoutingFields will read into a request body to recover routing fields when the normal capture is bypassed for size. Sized to comfortably hold a 1M-token context request (whose `model` field a client may place after a multi-MB `messages` array) while still capping pathological inputs.

Variables

This section is empty.

Functions

func CaptureRequest

func CaptureRequest(r *http.Request, cfg *Config, b Budget) (body []byte, truncated bool, originalSize int64, bypass string, release func(), err error)

CaptureRequest reads up to cfg.MaxRequestBytes from r.Body into a buffer suitable for middleware inspection, replacing r.Body with a replay reader so the upstream still sees the original bytes. When bypass != "" no body is read and r.Body is left untouched. The returned release function must be invoked once the request is fully processed; it returns the acquired budget tokens to the shared pool. release is always non-nil and is safe to defer immediately after the call.

func ScanRoutingFields

func ScanRoutingFields(r *http.Request, maxScan int64) (model string, stream bool, ok bool)

ScanRoutingFields recovers the LLM routing fields ("model" and "stream") from a request whose normal capture was bypassed or truncated for size. It reads up to maxScan bytes of r.Body to locate the top-level keys — clients (e.g. Claude Code) may place `model` after a multi-MB `messages` array — then restores r.Body so the upstream still receives the full, untouched stream. Only the small routing fields are extracted; the prompt is never buffered for capture, keeping memory bounded. Returns ok=false when the body isn't a JSON object, the model field isn't found within maxScan, or on a read error.

Types

type Budget

type Budget interface {
	Acquire(n int64) bool
	Release(n int64)
}

Budget is the global token-bucket semaphore shared across all in-flight captures so a single misbehaving target cannot exhaust the proxy.

func NewBudget

func NewBudget(total int64) Budget

NewBudget returns a Budget with the given total byte cap. A zero or negative total disables the budget check.

type CapturingResponseWriter

type CapturingResponseWriter struct {
	*responsewriter.PassthroughWriter
	// contains filtered or unexported fields
}

CapturingResponseWriter wraps an http.ResponseWriter, forwards bytes immediately to the client, and tees a bounded copy into an internal buffer for middleware inspection. Streaming-aware in the sense that every byte the upstream emits flows to the client without queuing — the tee just sees a bounded prefix. SSE-aware parsing happens in the response middleware against the buffered prefix; this writer makes no attempt to demux event boundaries.

Flusher and Hijacker are preserved via responsewriter.PassthroughWriter.

func NewCapturingResponseWriter

func NewCapturingResponseWriter(w http.ResponseWriter, maxBytes int64, b Budget) *CapturingResponseWriter

NewCapturingResponseWriter returns a writer that tees up to maxBytes into a capped buffer while forwarding bytes to the underlying writer immediately. When budget is non-nil the writer pre-acquires maxBytes from it and the returned wrapper must be released by calling Release() once the response is fully forwarded. If the budget cannot be acquired the writer falls back to forwarding the response unmodified, exposes Bypassed()=true with reason BypassBudget, and releases nothing.

func (*CapturingResponseWriter) Body

func (c *CapturingResponseWriter) Body() []byte

Body returns a copy of the buffered response prefix.

func (*CapturingResponseWriter) BypassReason

func (c *CapturingResponseWriter) BypassReason() string

BypassReason returns the bypass code recorded by the budget check. Empty when capture proceeded normally.

func (*CapturingResponseWriter) Bypassed

func (c *CapturingResponseWriter) Bypassed() bool

Bypassed reports whether the writer fell through to a no-tee passthrough because the response capture budget could not be acquired.

func (*CapturingResponseWriter) BytesWritten

func (c *CapturingResponseWriter) BytesWritten() int64

BytesWritten returns the total number of bytes forwarded to the underlying writer.

func (*CapturingResponseWriter) Release

func (c *CapturingResponseWriter) Release()

Release returns the response capture budget acquired at construction back to the shared pool. Idempotent. Safe to call from a defer immediately after construction even when the writer ended up bypassing the budget.

func (*CapturingResponseWriter) Status

func (c *CapturingResponseWriter) Status() int

Status returns the captured status code (defaults to 200 when WriteHeader has not been called).

func (*CapturingResponseWriter) Truncated

func (c *CapturingResponseWriter) Truncated() bool

Truncated reports whether the buffered prefix stopped short of the full response stream.

func (*CapturingResponseWriter) Write

func (c *CapturingResponseWriter) Write(p []byte) (int, error)

Write forwards p to the underlying writer unmodified and copies up to the remaining buffer capacity into the tee buffer.

func (*CapturingResponseWriter) WriteHeader

func (c *CapturingResponseWriter) WriteHeader(status int)

WriteHeader records the status code and forwards it to the underlying writer. Only the first call commits the status — matching HTTP semantics, where superfluous WriteHeader calls (and any call after the body has started) are ignored — so Status() reflects the code actually sent.

type Config

type Config struct {
	MaxRequestBytes  int64
	MaxResponseBytes int64
	ContentTypes     []string
}

Config holds per-target body capture limits after clamp validation. A zero MaxRequestBytes / MaxResponseBytes disables capture in that direction.

Jump to

Keyboard shortcuts

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