<feed xmlns='http://www.w3.org/2005/Atom'>
<title>mitmux/internal/proxy/websocket_test.go, branch main</title>
<subtitle>Terminal-based intercepting HTTP proxy.
</subtitle>
<id>https://srdusr.com/git/mitmux/atom?h=main</id>
<link rel='self' href='https://srdusr.com/git/mitmux/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/mitmux/'/>
<updated>2026-06-30T12:52:00+00:00</updated>
<entry>
<title>WebSocket interception</title>
<updated>2026-06-30T12:52:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-06-30T12:52:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/mitmux/commit/?id=384573a2dc5e3b8e2a7bdfe2ce949f2c52ba2c52'/>
<id>urn:sha1:384573a2dc5e3b8e2a7bdfe2ce949f2c52ba2c52</id>
<content type='text'>
The last "known limitation": a ws://wss:// connection stops being
one-shot request/response the instant its 101 Switching Protocols
lands, and forward()'s normal write-response-then-record flow has no
way to represent that. Scoped to HTTP/1.1 client legs (HTTP/2 can't be
hijacked for raw post-response access the way HTTP/1.1 can, and
browsers open a dedicated HTTP/1.1 connection for WebSocket regardless
of the surrounding page's protocol, so this isn't a real-world gap).

internal/proxy/websocket.go decodes each RFC 6455 frame's opcode and
payload for capture while relaying the exact same raw bytes it read
unmodified - this is capture, not tampering, matching the rest of the
codebase's raw-bytes-as-source-of-truth stance. One row per frame, not
per reassembled message (fragmentation is rare in real-world
WebSocket traffic; not worth buffering an unbounded number of pending
fragments to handle it). forward() branches on a matching 101 into
handleWebSocketUpgrade, which hijacks the client connection, relays
the handshake response raw, records the upgrade request/response to
history normally, then relays frames bidirectionally into a new
ws_messages table - reachable from the TUI's detail view via `w`.

Found and fixed two real bugs by actually driving a WebSocket
connection through a running daemon, not by reading the code:
stripHopByHop was deleting Connection/Upgrade from every outgoing
request (correct for an ordinary request per RFC 7230, catastrophic
for one asking to upgrade - every WebSocket attempt silently became a
426); and the relay tore the whole connection down the instant either
side saw a close frame, before the peer's own close-frame reply could
be relayed back, producing an abrupt EOF instead of a clean close.

Verified live end to end on both paths a real client uses: ws://
(plain HTTP forward-proxying) against a Python websockets echo
server, and wss:// (CONNECT-tunneled, TLS-intercepted) against the
same server behind TLS - text, binary, and extended-length frames,
plus a full close handshake with both directions' close frames
present, confirmed via the actual bytes captured in ws_messages.
</content>
</entry>
</feed>
