package
1.12.6
Repository: https://github.com/go-dev-frame/sponge.git
Documentation: pkg.go.dev

# README

WebSocket

ws is based on the github.com/gorilla/websocket library, support automatic client reconnection.


Example of use

1. Default setting

Server side code example:

package main

import (
    "context"
    "log"
    "github.com/go-dev-frame/sponge/pkg/ws"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
	
    r.GET("/ws", func(c *gin.Context) {
        s := ws.NewServer(c.Writer, c.Request, loopReceiveMessage) // default setting
        err := s.Run(context.Background())
        if err != nil {
            log.Println("webSocket server error:", err)
        }
    })
	
    err := r.Run(":8080")
    if err != nil {
        panic(err)
    }
}

func loopReceiveMessage(ctx context.Context, conn *ws.Conn) {
    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil { // release connection
            return
        }

        // handle message
        log.Println(messageType, message)
    }
}

Client side code example:

package main

import (
    "strconv"
    "log"
    "time"
    "github.com/go-dev-frame/sponge/pkg/ws"
    "github.com/gorilla/websocket"
)

var wsURL = "ws://localhost:8080/ws"

func main() {
    c, err := ws.NewClient(wsURL) // default setting
    if err != nil {
        log.Println("connect error:", err)
        return
    }
    defer c.Close()

    go func() {
        for {
            _, message, err := c.GetConn().ReadMessage()
            if err != nil {
                log.Println("client read error:", err)
                return
            }
            log.Printf("client received: %s", message)
        }
    }()
    
    for i := 0; i < 5; i++ {
        data := "Hello, World " + strconv.Itoa(i)
        err = c.GetConn().WriteMessage(websocket.TextMessage, []byte(data))
        if err != nil {
            log.Println("write error:", err)
        }
        time.Sleep(100 * time.Millisecond)
    }
}

2. Custom setting

Server side custom setting, options such as ws.Upgrader, ws.WithNoClientPingTimeout, ws.WithServerLogger, ws.WithResponseHeader can be set.

package main

import (
	"context"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"github.com/go-dev-frame/sponge/pkg/logger"
	"github.com/go-dev-frame/sponge/pkg/ws"
)

func main() {
	r := gin.Default()
	ug := &websocket.Upgrader{
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
	r.GET("/ws", func(c *gin.Context) {
		s := ws.NewServer(c.Writer, c.Request, loopReceiveMessage,
			ws.WithUpgrader(ug),
			ws.WithNoClientPingTimeout(time.Minute), // client side must send ping message in every 1 minutes
			ws.WithServerLogger(logger.Get()),
		)
		err := s.Run(context.Background())
		if err != nil {
			logger.Warn("WebSocket server error:", logger.Err(err))
		}
	})

	err := r.Run(":8080")
	if err != nil {
		panic(err)
	}
}

func loopReceiveMessage(ctx context.Context, conn *ws.Conn) {
	for {
		messageType, message, err := conn.ReadMessage()
		if err != nil {
			logger.Warn("ReadMessage error", logger.Err(err))
			return
		}
		logger.Infof("server side received: %s", message)

		switch messageType {
		case websocket.TextMessage:
			err = conn.WriteMessage(messageType, message)
			if err != nil {
				logger.Warn("WriteMessage error", logger.Err(err))
				continue
			}

		case websocket.PingMessage:
			err = conn.WriteMessage(websocket.PongMessage, []byte("pong"))
			if err != nil {
				logger.Warn("Write pong message error:", logger.Err(err))
				continue
			}
		default:
			logger.Warnf("Unknown message type: %d", messageType)
		}
	}
}

Client side custom setting, options such as ws.Dialer, ws.WithPing, ws.WithClientLogger, ws.WithRequestHeader can be set.

package main

import (
	"strconv"
	"time"

	"github.com/gorilla/websocket"
	"github.com/go-dev-frame/sponge/pkg/logger"
	"github.com/go-dev-frame/sponge/pkg/ws"
)

var wsURL = "ws://localhost:8080/ws"

func main() {
	c, err := ws.NewClient(wsURL,
		ws.WithPing(time.Second*20), //  It is recommended that the ping timeout time set by the server be less than 1/2
		ws.WithClientLogger(logger.Get()),
	)
	if err != nil {
		logger.Warn("connect error", logger.Err(err))
		return
	}
	defer c.Close()

	go clientLoopReadMessage(c)

	i := 0
	for {
		time.Sleep(time.Second * 3)
		i++
		data := "Hello, World " + strconv.Itoa(i)
		err = c.GetConn().WriteMessage(websocket.TextMessage, []byte(data))
		if err != nil {
			logger.Warn("WriteMessage error", logger.Err(err))
		}
	}
}

func clientLoopReadMessage(c *ws.Client) {
	for {
		select {
		case <-c.GetCtx().Done():
			return
		default:
			_, message, err := c.GetConn().ReadMessage()
			if err != nil {
				logger.Warn("ReadMessage error", logger.Err(err))
				time.Sleep(time.Second * 5)
				continue
			}
			logger.Infof("client side received: %s", message)
		}

	}
}

# Functions

IsClientClose returns true if the error is caused by client close.
NewClient creates a new client.
NewServer creates a new WebSocket server.
WithClientLogger sets the logger for the client.
WithDialer sets the dialer for the client.
WithMaxMessageWaitPeriod sets the maximum waiting period for a message before closing the connection.
WithNoClientPingTimeout sets the timeout for the client to send a ping message, if timeout, the connection will be closed.
WithPing sets the interval for sending ping message to the server.
WithRequestHeader sets the request header for the client.
WithResponseHeader sets the response header for the WebSocket upgrade response.
WithServerLogger sets the logger for the server.
WithUpgrader sets the WebSocket upgrader for the server.

# Structs

Client is a wrapper of gorilla/websocket.
Server is a WebSocket server.

# Type aliases

ClientOption is a functional option for the client.
Conn is a WebSocket connection.
LoopFn is the function that is called for each WebSocket connection.
ServerOption is a functional option for the Server.