How to Upgrade a TCP Server to WebSocket in Go – Step‑by‑Step Guide
This article walks through converting a custom TCP protocol server to use WebSocket on the same port using Go's official websocket library, covering client connection code, server upgrade handling with a fake http.ResponseWriter, request parsing, and a complete WebsocketOnTCP implementation.
WebSocket Library
The official Go WebSocket package golang.org/x/net/websocket can be cloned from https://github.com/golang/net.git into go/src/golang.org/x/net. Its API is straightforward and documented at websocket · pkg.go.dev.
Client Usage
conn, err := websocket.Dial(url, subprotocol, origin)
websocket.Message.Send(conn, "") // send a string
var b []byte
websocket.Message.Receive(conn, &b) // receive a []byteThe subprotocol argument specifies a sub‑protocol (can be empty) and origin is the request origin like http://host. Using websocket.Message ensures each packet is sent/received as a complete frame.
Server Upgrade
On the server side, websocket.Handler or websocket.Server upgrades an HTTP request to a WebSocket connection, providing a *websocket.Conn for business logic.
var recv func([]byte)
var err error
f := func(conn *websocket.Conn) {
err = doWebSocket(conn, recv)
}
websocket.Handler(f).ServeHTTP(w, r)Both Handler and Server can be registered with net/http, or their ServeHTTP method can be called directly.
Fake http.ResponseWriter for TCP
To upgrade a raw *net.TCPConn to a WebSocket, we need to supply an http.ResponseWriter and an *http.Request. A minimal writer implementation is:
type wsFakeWriter struct {
conn *net.TCPConn
rw *bufio.ReadWriter
}
func makeHttpResponseWriter(conn *net.TCPConn) *wsFakeWriter {
w := new(wsFakeWriter)
w.conn = conn
w.rw = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
return w
}
func (w *wsFakeWriter) Header() http.Header { return nil }
func (w *wsFakeWriter) WriteHeader(int) {}
func (w *wsFakeWriter) Write(b []byte) (int, error) { return 0, nil }
func (w *wsFakeWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.conn, w.rw, nil
}Parsing the HTTP Request
The raw bytes read from the TCP connection can be combined with the connection itself to form a bufio.Reader, which http.ReadRequest can parse:
func joinBufferAndReader(buffer []byte, reader io.Reader) io.Reader {
return io.MultiReader(bytes.NewReader(buffer), reader)
}
func takeHttpRequest(buffer []byte, conn *net.TCPConn) (*http.Request, error) {
r := joinBufferAndReader(buffer, conn)
return http.ReadRequest(bufio.NewReader(r))
}WebsocketOnTCP Helper
The core function that ties everything together is:
func WebsocketOnTCP(buffer []byte, conn *net.TCPConn, recv func(*Package)) error {
req, err := takeHttpRequest(buffer, conn)
if err != nil {
return err
}
f := func(ws *websocket.Conn) {
err = doWebSocket(ws, recv)
}
w := makeHttpResponseWriter(conn)
websocket.Handler(f).ServeHTTP(w, req)
return err
}
func doWebSocket(conn *websocket.Conn, recv func(*Package)) error {
remoteAddr := conn.RemoteAddr()
reply := func(b []byte) error { websocket.Message.Send(conn, b); return nil }
for {
var b []byte
if err := websocket.Message.Receive(conn, &b); err != nil {
return err
}
pack := new(Package)
pack.Addr = remoteAddr
pack.Content = b
pack.Reply = reply
recv(pack)
}
}This implementation handles a simple receive‑and‑reply loop; it does not guarantee thread safety or handle upgrade failures.
Notes
The approach works because the TCP protocol’s first three bytes differ from the WebSocket handshake (which starts with GET), allowing the server to decide whether to upgrade. The code relies heavily on Go’s standard library and interfaces, keeping the conversion concise.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Go Development Architecture Practice
Daily sharing of Golang-related technical articles, practical resources, language news, tutorials, real-world projects, and more. Looking forward to growing together. Let's go!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
