In the previous chapter, Proxy Orchestration & Lifecycle, we acted as the Stage Manager. We set up the environment, got our security certificates ready, and prepared to start the show.
Now, we need to look at the star of the show: the Relay.
Imagine you are in a secure container (your coding environment). You want to run a command like npm install or curl google.com.
If curl tries to drive directly into the river, it drowns (connection timeout).
The CONNECT-over-WebSocket Relay is a ferry service.
curl drives its car to our local server (instead of the river).This chapter explains how we build this ferry terminal in code.
To build this, we need to understand three small concepts:
We need a standard TCP server listening on 127.0.0.1 (localhost). This acts as the "Ferry Terminal." It tricks tools like git or npm into thinking they have reached the internet.
When a tool (like curl) uses a proxy, it sends a polite request first:
CONNECT google.com:443 HTTP/1.1
It is asking: "Can you please set up a tunnel to Google for me?" We need to parse this text.
Standard internet traffic is TCP. Our ferry route is a WebSocket. We cannot just copy-paste the data; we have to wrap it in an envelope (Chunk) so it travels safely.
Before looking at the code, let's visualize the sequence of events when you run a command like curl https://google.com.
Let's look at relay.ts. We have simplified the code to show only the logic, removing complex error handling and buffer management for clarity.
We start a server on a random port. This is the entry point for all traffic.
// relay.ts (Simplified)
import { createServer } from 'node:net'
export async function startUpstreamProxyRelay(opts) {
// Create a standard TCP server
const server = createServer((socket) => {
// A new "car" has arrived at the terminal!
handleConnection(socket, opts);
});
// Listen on localhost, port 0 means "assign me a random port"
server.listen(0, '127.0.0.1');
return { port: server.address().port, stop: () => server.close() }
}
Explanation: This code creates the "Ferry Terminal." It waits for local tools to connect to it.
When a connection comes in, it's just raw data. We need to check if it's a valid CONNECT request.
// Inside handleConnection...
socket.on('data', (data) => {
const text = data.toString();
// Check if the car is asking for a ride
if (text.startsWith('CONNECT')) {
// Extract destination (e.g., "google.com:443")
const target = text.split(' ')[1];
// Start the ferry!
openTunnel(socket, target, opts);
}
});
Explanation: We look at the first few bytes. If the tool says CONNECT, we know where it wants to go. We pause the traffic and prepare to open the WebSocket.
Now we connect to the Cloud using a secure WebSocket.
function openTunnel(socket, target, opts) {
// Create the WebSocket to our cloud infrastructure
const ws = new WebSocket(opts.wsUrl, {
headers: { Authorization: `Bearer ${opts.token}` }
});
ws.onopen = () => {
// 1. Tell the cloud where we want to go
ws.send(encodeChunk(`CONNECT ${target}`));
// 2. Tell the local tool the trip has started
socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
}
}
Explanation:
200 OK to curl or npm. Now curl thinks it is talking directly to Google.Once the tunnel is open, we simply shuffle data back and forth.
// 1. Traffic from Tool -> Cloud
socket.on('data', (data) => {
// Wrap the TCP bytes in a special envelope
const chunk = encodeChunk(data);
ws.send(chunk);
});
// 2. Traffic from Cloud -> Tool
ws.onmessage = (event) => {
// Unwrap the envelope
const data = decodeChunk(event.data);
// Give the raw TCP bytes back to the tool
socket.write(data);
}
Explanation:
encodeChunk to wrap the raw data. This prevents the WebSocket from getting confused about where one message ends and another begins.curl) doesn't know about the WebSocket. It just writes bytes and gets bytes back.
While the concept is simple, the implementation in relay.ts handles some tricky edge cases:
CONNECT header is split across two packets. We have to buffer the data until we have the full header.CONNECT ... and the first bit of data (like "Hello") in the exact same packet. We must not lose that "Hello" while setting up the WebSocket.We have successfully built a "Ferry" (Relay) that:
However, we glazed over one detail: encodeChunk. How exactly do we wrap these data packets? If we just sent raw text, how would the server know the difference between connection metadata and actual user data?
We need a strict protocol for this packaging.
Next Chapter: Protobuf Chunking Protocol
Generated by Code IQ