Reverse HTTP proxy over WebSocket in Go (Part 2)
Tags: go, webdev, websocket
Published: 2021-12-15 07:31:26

## Series introduction

In my [previous post](https://dev.to/hgsgtk/reverse-http-proxy-over-websocket-in-go-part-1-13n4) I talked about how to start WebSocket server in Go.

{% link https://dev.to/hgsgtk/reverse-http-proxy-over-websocket-in-go-part-1-13n4 %}

In this post, I will be starting to talk about how to establish a WebSocket connection in Go.

- Start a WebSocket server ([Part 1](https://dev.to/hgsgtk/reverse-http-proxy-over-websocket-in-go-part-1-13n4))
- Establish a WebSocket connection ([Part 2](https://dev.to/hgsgtk/reverse-http-proxy-over-websocket-in-go-part-2-o4a))
- Keep a established connection ([Part 3](https://dev.to/hgsgtk/reverse-http-proxy-over-websocket-in-go-part-3-2ggd) | Part 4)
- Relay TCP connection from "App" to the peer of WebSocket
- Relay TCP connection in WebSocket data to "internal API"

## Reverse HTTP proxy over WebSocket

A reverse HTTP proxy over WebSocket is a type of proxies, which retrieves resources on behalf on a client from servers and uses the WebSocket protocol as a "tunnel" to pass TCP communication from server to client.

![A network diagram for reverse proxy over WebSocket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iupaoeq283lyeqpkg049.png)

I'll use [root-gg/wsp](https://github.com/root-gg/wsp) as a sample code to explain it. I'll use the forked code to explain because maintenance has stopped and the Go language and libraries version needed to be updated.

{% github hgsgtk/wsp no-readme %}

## Establish a WebSocket connection

In my [previous post](https://dev.to/hgsgtk/reverse-http-proxy-over-websocket-in-go-part-1-13n4), I explained the implementation for starting a WebSocket server.

![A diagram describing to establish WebSocket connection from client](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6h0wc2gvgyer36krwjrz.png)

To establish a WebSocket connection, the WebSocket client needs to request a handshake to the server.

![A diagram describing a connection using WebSocket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z7qiia95sgoly2rya998.png)

When you run this client program, it will start to negotiate the handshake, then finished to establish the connection.

```bash
$ go run cmd/wsp_client/main.go -config examples/wsp_client.cfg

2021/12/15 15:10:54 Connecting to ws://127.0.0.1:8080/register
2021/12/15 15:10:54 Connected to ws://127.0.0.1:8080/register
```

Let's read the Go code. First is the main function ([cmd/client/main.go](https://github.com/hgsgtk/wsp/blob/a086362f2d8c0dfaecc8e684762f4706724a85d6/cmd/wsp_client/main.go)), which is the entry point.

```go
package main

import (
	// (omit)
)

func main() {
	// (omit)

	proxy := client.NewClient(config)
	proxy.Start(ctx)

	// (omit: shutdown)
}
```

## Initialize a WebSocket client

The first line is:

```go
proxy := client.NewClient(config)
```

[client.NewClient](https://github.com/hgsgtk/wsp/blob/65f78095d0814db2d00e76316e860d96873f8baa/client/client.go#L20) function returns a pointer of [client.Client](https://github.com/hgsgtk/wsp/blob/65f78095d0814db2d00e76316e860d96873f8baa/client/client.go#L11) struct which connects to servers using the WebSocket protocol.

```go
type Client struct {
	Config *Config

	client *http.Client
	dialer *websocket.Dialer
	pools  map[string]*Pool
}
```

An important fields is `dialer` whose type is [websocket.Dialer](https://pkg.go.dev/github.com/gorilla/websocket#Dialer) to contains options for connection to WebSocket server. The [websocket.Dialer](https://pkg.go.dev/github.com/gorilla/websocket#Dialer) struct is like that:

```go
type Dialer struct {
    NetDial func(network, addr string) (net.Conn, error)
    NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
    Proxy func(*http.Request) (*url.URL, error)
    TLSClientConfig *tls.Config
    HandshakeTimeout time.Duration
    ReadBufferSize, WriteBufferSize int
    WriteBufferPool BufferPool
    Subprotocols []string
    EnableCompression bool
    Jar http.CookieJar
}
```

There are some points to understand the WebSocket protocol, see the following post more detail.

{% link https://dev.to/hgsgtk/websocket-client-3gni %}

## Send a handshake request to a WebSocket server

The next line is:

```go
proxy.Start()
```

Several functions are called from this code but the important one is [the following code](https://github.com/hgsgtk/wsp/blob/6a3b551753570c737a3a7f836c4a43bbf22a809a/client/connection.go#L46).

```go
connection.ws, _, err = connection.pool.client.dialer.DialContext(
	ctx,
	connection.pool.target,
	http.Header{"X-SECRET-KEY": {connection.pool.secretKey}},
)
```

[Dialer.DialContext](https://pkg.go.dev/github.com/gorilla/websocket#Dialer.DialContext) creates a new client connection to open handshake, then it returns a pointer of [websocket.Conn](https://pkg.go.dev/github.com/gorilla/websocket#Conn) struct which represents a WebSocket connection.

If successful, WebSocket handshake is complete. You can start exchanging messages over the established WebSocket connection.

## Greeting messages

Immediately after handshake, we sometimes do greeting to send client meta data (i.e. device ID) or authentication information via WebsSocket messasge.

For this scene, you can receive and send messages by calling the Conn's [ReadMessage](https://pkg.go.dev/github.com/gorilla/websocket#Conn.ReadMessage) and [WriteMessage](https://pkg.go.dev/github.com/gorilla/websocket#Conn.WriteMessage).

Data is transmitted using a sequence of frames. This wire format for the data transfer part is described by the [ABNF](https://datatracker.ietf.org/doc/html/rfc5234) (Augmented BNF for Syntax Specification) in the WebSocket protocol. A overview of the framing is given in the following figure.

![A figure of base framing protocol](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ya8sxy582lm88yp9rssk.png)

In this example, the client sends a message to the server.

```go
greeting := fmt.Sprintf(
	"%s_%d",
	connection.pool.client.Config.ID,
	connection.pool.client.Config.PoolIdleSize,
)
err = connection.ws.WriteMessage(websocket.TextMessage, []byte(greeting))
if err != nil {
	log.Println("greeting error :", err)
	connection.Close()
	return
}
```

[Conn.WriteMessage](https://pkg.go.dev/github.com/gorilla/websocket#Conn.WriteMessage)

```go
func (c *Conn) WriteMessage(messageType int, data []byte) error
```

And then, `websocket.TextMessage` is one of opcodes which is defined in [RFC 6455 - 11.8. WebSocket Opcode Registry](https://datatracker.ietf.org/doc/html/rfc6455#section-11.8).

| Opcode | Meaning |
| -- | -- |
| 0 | Continuation Frame |
| 1 | Text Frame |
| 2 | Binary Frame |
| 8 | Connection Close Frame |
| 9 | Ping Frame |
| 10 | Pong Frame |

`websocket.TextMessage` means the opcode `1` (Text Frame), which is for exchanging text data over a WebSocket connection. 

The server will receive the message sent by the above code ([code](https://github.com/hgsgtk/wsp/blob/fbf84af6f523ec74139193a54805f5c59903d371/server/server.go#L271).

```go
// Handshaking
ws, err := server.upgrader.Upgrade(w, r, nil)
if err != nil {
	wsp.ProxyErrorf(w, "HTTP upgrade error : %v", err)
	return
}

// Receive the message from the client
_, greeting, err := ws.ReadMessage()
if err != nil {
	wsp.ProxyErrorf(w, "Unable to read greeting message : %s", err)
	ws.Close()
	return
}
```

[ReadMessage](https://pkg.go.dev/github.com/gorilla/websocket#Conn.ReadMessage) is a method to read messages from the peer which interprets data in base framing protocol.

```go
func (c *Conn) ReadMessage() (messageType int, p []byte, err error)
```

When you want to exchange simple text messages over WebSocket connection, you can do it with just two methods I just introduced in this post.

## Conclusion

This post explained how to establish a WebSocket connection.

I will explain the rest points in part 3 and beyond.

- Keep a established connection

{% link https://dev.to/hgsgtk/reverse-http-proxy-over-websocket-in-go-part-3-2ggd %}

- Relay TCP connection from "App" to the peer of WebSocket
- Relay TCP connection in WebSocket data to "internal API"