inspect

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Apr 19, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package inspect captures, stores, and replays HTTP requests that pass through the tunnel client.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Capture

func Capture(ctx context.Context, stream net.Conn, localAddr string, store Store, opts Options) error

Capture reads an HTTP request from stream, proxies it to localAddr, reads the response, proxies it back, and stores a Record in store.

It implements the three-rule body policy:

  1. Request with Upgrade → raw passthrough (headers captured, no bodies).
  2. Response with text/event-stream → headers captured, body raw-copied.
  3. Otherwise → bodies tee'd up to the caps; overflow flips to raw.

This function is the per-stream equivalent of (*client.Client).handleStream with inspection enabled.

func NewID

func NewID() string

NewID returns a time-sortable 16-character ID built from the current time plus randomness. Later IDs sort greater than earlier ones.

Types

type BodyReason

type BodyReason string

BodyReason describes why a body was truncated.

const (
	BodyFull     BodyReason = ""
	BodyStreamed BodyReason = "streamed"
	BodyTooLarge BodyReason = "too_large"
	BodyUpgrade  BodyReason = "upgrade"
)

type Filter

type Filter struct {
	Method      string // exact, case-insensitive; "" = any
	StatusClass int    // 2|3|4|5 for 2xx etc; 0 = any
	PathSub     string // substring match on path
	Limit       int    // max rows; 0 = default (100)
	Cursor      string // record ID; returned page has IDs strictly older than this
}

Filter narrows a List query.

type MemoryStore

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

MemoryStore is an in-memory ring buffer Store. Oldest records are dropped when capacity is reached.

func NewMemoryStore

func NewMemoryStore(capacity int) *MemoryStore

NewMemoryStore returns a MemoryStore with the given capacity. Capacities below 1 are clamped to 1.

func (*MemoryStore) Add

func (m *MemoryStore) Add(_ context.Context, r *Record) error

Add inserts a record and evicts the oldest if at capacity.

func (*MemoryStore) Close

func (m *MemoryStore) Close() error

Close closes all subscriber channels.

func (*MemoryStore) Get

func (m *MemoryStore) Get(_ context.Context, id string) (*Record, error)

Get returns a record by ID or an error if not found.

func (*MemoryStore) List

func (m *MemoryStore) List(_ context.Context, f Filter) ([]*Record, error)

List returns records newest-first, respecting the filter and cursor.

func (*MemoryStore) Subscribe

func (m *MemoryStore) Subscribe() (<-chan *Record, func())

Subscribe registers a listener. The returned channel has a small buffer; if it fills, records are dropped for that subscriber.

type Options

type Options struct {
	// MaxReqBody caps how many request-body bytes are stored per record.
	// Bytes beyond the cap still flow to the local service.
	MaxReqBody int

	// MaxRespBody caps how many response-body bytes are stored per record.
	// Bytes beyond the cap still flow back to the tunnel server.
	MaxRespBody int

	// Subdomain is stamped on each record (informational).
	Subdomain string
}

Options configures the capture path.

func DefaultOptions

func DefaultOptions() Options

DefaultOptions returns sensible defaults.

type Record

type Record struct {
	ID         string      `json:"id"`
	ReceivedAt time.Time   `json:"received_at"`
	Subdomain  string      `json:"subdomain,omitempty"`
	Host       string      `json:"host"`
	Method     string      `json:"method"`
	Path       string      `json:"path"`
	Proto      string      `json:"proto,omitempty"`
	RemoteAddr string      `json:"remote_addr,omitempty"`
	ReqHeaders http.Header `json:"req_headers,omitempty"`
	ReqBody    []byte      `json:"req_body,omitempty"`
	ReqSize    int64       `json:"req_size"`
	ReqReason  BodyReason  `json:"req_reason,omitempty"`

	Status      int         `json:"status"`
	RespHeaders http.Header `json:"resp_headers,omitempty"`
	RespBody    []byte      `json:"resp_body,omitempty"`
	RespSize    int64       `json:"resp_size"`
	RespReason  BodyReason  `json:"resp_reason,omitempty"`

	DurationMs int64  `json:"duration_ms"`
	Err        string `json:"err,omitempty"`

	Replay bool   `json:"replay,omitempty"`
	OfID   string `json:"of_id,omitempty"`
}

Record is a single captured request/response pair.

type Replayer

type Replayer struct {
	Store     Store
	LocalAddr string
	Options   Options
}

Replayer holds the context a Replay needs: where to send the replayed request and which Store to log the result in.

func (*Replayer) Replay

func (rp *Replayer) Replay(ctx context.Context, id string) (*Record, error)

Replay fires the captured request in r against the configured localAddr and stores the result as a new Record with Replay=true, OfID=r.ID. Returns the new record.

type SQLiteStore

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

SQLiteStore persists records in a SQLite database on disk.

func NewSQLiteStore

func NewSQLiteStore(path string) (*SQLiteStore, error)

NewSQLiteStore opens a SQLite database at path (":memory:" for tests) and ensures the schema. All records are persisted indefinitely; callers manage retention out-of-band (backup/rotate the file, or VACUUM).

func (*SQLiteStore) Add

func (s *SQLiteStore) Add(ctx context.Context, r *Record) error

Add inserts a record.

func (*SQLiteStore) Close

func (s *SQLiteStore) Close() error

Close shuts the store down.

func (*SQLiteStore) Get

func (s *SQLiteStore) Get(ctx context.Context, id string) (*Record, error)

Get returns a single record by ID.

func (*SQLiteStore) List

func (s *SQLiteStore) List(ctx context.Context, f Filter) ([]*Record, error)

List returns records newest-first.

func (*SQLiteStore) Subscribe

func (s *SQLiteStore) Subscribe() (<-chan *Record, func())

Subscribe behaves like MemoryStore.Subscribe (in-process broadcast only).

type Store

type Store interface {
	Add(ctx context.Context, r *Record) error
	List(ctx context.Context, f Filter) ([]*Record, error)
	Get(ctx context.Context, id string) (*Record, error)
	// Subscribe returns a channel that receives records as they're added and
	// an unsubscribe function. The channel buffer is small; slow consumers
	// may miss records.
	Subscribe() (<-chan *Record, func())
	Close() error
}

Store is the persistence interface for records. Implementations must be safe for concurrent use.

Jump to

Keyboard shortcuts

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