Skip to content
/ gws Public

simple, fast, reliable websocket server & client, supports running over tcp/kcp/unix domain socket. keywords: ws, proxy, chat, go, golang...

License

Notifications You must be signed in to change notification settings

lxzan/gws

Repository files navigation

gws

a event driven websocket framework

Features

  • event-driven api design
  • use queues to process messages parallelly, with concurrency control
  • middleware support
  • self-implementing buffer, more memory-saving
  • no dependency

Quick Start

chat room example:

server

package main

import (
	"context"
	"encoding/json"
	"github.com/lxzan/gws"
	"net/http"
	"sync"
)

type Handler struct {
	sessions sync.Map
}

func (h *Handler) deleteSession(socket *gws.Conn) {
	name, _ := socket.Storage.Get("name")
	h.sessions.Delete(name)
}

func (h *Handler) OnOpen(socket *gws.Conn) {
	name, _ := socket.Storage.Get("name")
	h.sessions.Store(name.(string), socket)

	go func(conn *gws.Conn) {
		for {
			<-conn.Context.Done()
			h.deleteSession(conn)
		}
	}(socket)
}

func (h *Handler) OnClose(socket *gws.Conn, code gws.Code, reason []byte) {}

type Request struct {
	To      string `json:"to"`
	Message string `json:"message"`
}

func (h *Handler) OnMessage(socket *gws.Conn, m *gws.Message) {
	var request Request
	json.Unmarshal(m.Bytes(), &request)
	defer m.Close()

	if receiver, ok := h.sessions.Load(request.To); ok {
		receiver.(*gws.Conn).Write(gws.OpcodeText, m.Bytes())
	}
}

func (h *Handler) OnError(socket *gws.Conn, err error) {}

func (h *Handler) OnPing(socket *gws.Conn, m []byte) {}

func (h *Handler) OnPong(socket *gws.Conn, m []byte) {}

func main() {
	var upgrader = gws.Upgrader{
		ServerOptions: &gws.ServerOptions{
			LogEnabled:      true,
			CompressEnabled: false,
		},
		CheckOrigin: func(r *gws.Request) bool {
			r.Storage.Put("name", r.URL.Query().Get("name"))
			return true
		},
	}

	var handler = &Handler{sessions: sync.Map{}}
	var ctx = context.Background()

	http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		upgrader.Upgrade(ctx, w, r, handler)
	})

	http.ListenAndServe(":3000", nil)
}

client(browser console)

let ws1 = new WebSocket('ws://127.0.0.1:3000/ws?name=caster');
let ws2 = new WebSocket('ws://127.0.0.1:3000/ws?name=lancer');
ws1.send('{"to": "lancer", "msg": "Hello! I am caster"}');

API

type EventHandler interface {
    OnOpen(socket *Conn)
    OnClose(socket *Conn, code Code, reason []byte)
    OnMessage(socket *Conn, m *Message)
    OnError(socket *Conn, err error)
    OnPing(socket *Conn, m []byte)
    OnPong(socket *Conn, m []byte)
}

Usage

Middleware

  • use internal middleware
var upgrader = gws.Upgrader{}
upgrader.Use(gws.Recovery(func(exception interface{}) {
    fmt.Printf("%v", exception)
}))
  • write a middleware
upgrader.Use(func (socket *gws.Conn, msg *gws.Message) {
    var t0 = time.Now().UnixNano()
    msg.Next(socket)
    var t1 = time.Now().UnixNano()
    fmt.Printf("cost=%dms\n", (t1-t0)/1000000)
})

Heartbeat

  • Sever Side Heartbeat
func (h *Handler) OnOpen(socket *gws.Conn) {
    go func (ws *gws.Conn) {
        ticker := time.NewTicker(15 * time.Second)
        defer ticker.Stop()
    
        for {
            select {
            case <-ticker.C:
            	ws.WritePing(nil)
            case <-ws.Context.Done():
            	return
            }
        }

    }(socket)
}

func (h *Handler) OnPong(socket *gws.Conn, m []byte) {
    _ = socket.SetDeadline(30 * time.Second)
}
  • Client Side Heartbeat
func (h *Handler) OnPing(socket *gws.Conn, m []byte) {
    socket.WritePong(nil)
    _ = socket.SetDeadline(30 * time.Second)
}

About

simple, fast, reliable websocket server & client, supports running over tcp/kcp/unix domain socket. keywords: ws, proxy, chat, go, golang...

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published