Documentation
¶
Overview ¶
Package inspect captures, stores, and replays HTTP requests that pass through the tunnel client.
Index ¶
- func Capture(ctx context.Context, stream net.Conn, localAddr string, store Store, ...) error
- func NewID() string
- type BodyReason
- type Filter
- type MemoryStore
- func (m *MemoryStore) Add(_ context.Context, r *Record) error
- func (m *MemoryStore) Close() error
- func (m *MemoryStore) Get(_ context.Context, id string) (*Record, error)
- func (m *MemoryStore) List(_ context.Context, f Filter) ([]*Record, error)
- func (m *MemoryStore) Subscribe() (<-chan *Record, func())
- type Options
- type Record
- type Replayer
- type SQLiteStore
- func (s *SQLiteStore) Add(ctx context.Context, r *Record) error
- func (s *SQLiteStore) Close() error
- func (s *SQLiteStore) Get(ctx context.Context, id string) (*Record, error)
- func (s *SQLiteStore) List(ctx context.Context, f Filter) ([]*Record, error)
- func (s *SQLiteStore) Subscribe() (<-chan *Record, func())
- type Store
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:
- Request with Upgrade → raw passthrough (headers captured, no bodies).
- Response with text/event-stream → headers captured, body raw-copied.
- 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.
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) 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.
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 ¶
Replayer holds the context a Replay needs: where to send the replayed request and which Store to log the result in.
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) 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.