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.

Go Development Architecture Practice
Go Development Architecture Practice
Go Development Architecture Practice
How to Upgrade a TCP Server to WebSocket in Go – Step‑by‑Step Guide

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 []byte

The 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

GolangtcpWebSocketNetworkingServer
Go Development Architecture Practice
Written by

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!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.