- Extract newRecorderObject, checkRateLimit, sniffingDial, and
handleSniffedProtocol from the Handle method into dedicated files
(sniffing.go, util.go) to reduce Handle from ~100 to ~65 lines.
- Introduce SnifferBuilder: populated once during Init, reused via
Build() per connection. Avoids reconstructing a 9-field sniffing.Sniffer
literal on every inbound connection.
- Hoist router nil check from the lazy dial closure into Handle so
misconfigured handlers fail immediately instead of silently dropping
unrecognised traffic with no error.
- Add nil-addr guard in checkRateLimit (missing from the old inline
version — would panic if the remote address were somehow nil).
- Add comprehensive unit tests (44 tests across 5 test files) covering
handler creation, Init, metadata parsing (defaults, custom, MITM,
error paths), protocol dispatch (HTTP, TLS, unknown, empty), rate
limiting, recorder objects, dial plumbing, and SnifferBuilder.
- Add package-level godoc explaining the handler scope, limitations
vs tcp/forward, and the connection processing flow.
- Fix metadata comment: s/SnifferBuilder/sniffing.Sniffer/.
- serial/handler.go: capture xnet.Pipe return value in Handle() and forwardSerial()
- serial/handler.go: log Warnf when chain dial returns (nil,nil) before falling back
- sni/handler.go: use defer for read deadline reset to prevent stale deadline
- sni/handler.go: unrecognized protocol returns nil (clean close) instead of error
- sni/metadata.go: update readTimeout comment to reflect dual usage (deadline + SnifferBuilder)
- Set read deadline before/after sniffing.Sniff to prevent goroutine
leak from slow clients (matches redirect/tcp and ss patterns)
- Log sniff errors at Debug level instead of silently discarding them
- Add nil Router check in dial closure to prevent NPE panic