event-driven go websocket server
zero dependency, not use channel but event driven
zero extra goroutine to manage connection
zero error to read/write operation, errors have been handled appropriately
built-in concurrent_map implementation
fully passes the WebSocket autobahn-testsuite
It's designed for api server, do not write big message.
WebSocket events are emitted synchronously, manage goroutines yourself.
Write operations are synchronous blocking, for broadcast scenarios, special attention is needed.
machine: Ubuntu 20.04LTS VM (4C8T)
client: tcpkali
payload: 2.34KiB
Server
Connection
Send Speed (msg/s)
T (s)
Download / Upload Bandwidth (Mbps)
gws
200
4000
30
10916.094↓ 10951.587↑
gorilla
200
4000
30
4344.222↓ 4380.711↑
gws
2000
300
30
7941.090↓ 7951.117↑
gorilla
2000
300
30
4706.938↓ 4715.744↑
gws
5000
60
30
5891.151↓ 5908.599↑
gorilla
5000
60
30
-
gws
10000
10
60
1980.124↓ 1977.561↑
gorilla
10000
10
60
1972.556↓ 1979.981↑
gws
10000
20
60
3952.788↓ 3959.341↑
gorilla
10000
20
60
-
- means exception
type Event interface {
OnOpen (socket * Conn )
OnError (socket * Conn , err error )
OnClose (socket * Conn , code uint16 , reason []byte )
OnPing (socket * Conn , payload []byte )
OnPong (socket * Conn , payload []byte )
OnMessage (socket * Conn , message * Message )
}
go get -v github.com/lxzan/gws@latest
Quick Start (Autobahn Server)
package main
import (
"fmt"
"github.com/lxzan/gws"
"net/http"
)
func main () {
var upgrader = gws .NewUpgrader (func (c * gws.Upgrader ) {
c .CompressEnabled = true
c .CheckTextEncoding = true
c .MaxContentLength = 32 * 1024 * 1024
c .EventHandler = new (WebSocket )
})
http .HandleFunc ("/connect" , func (writer http.ResponseWriter , request * http.Request ) {
socket , err := upgrader .Accept (writer , request )
if err != nil {
return
}
socket .Listen ()
})
_ = http .ListenAndServe (":3000" , nil )
}
type WebSocket struct {}
func (c * WebSocket ) OnClose (socket * gws.Conn , code uint16 , reason []byte ) {
fmt .Printf ("onclose: code=%d, payload=%s\n " , code , string (reason ))
}
func (c * WebSocket ) OnError (socket * gws.Conn , err error ) {
fmt .Printf ("onerror: err=%s\n " , err .Error ())
}
func (c * WebSocket ) OnOpen (socket * gws.Conn ) {
println ("connected" )
}
func (c * WebSocket ) OnPing (socket * gws.Conn , payload []byte ) {
fmt .Printf ("onping: payload=%s\n " , string (payload ))
socket .WritePong (payload )
}
func (c * WebSocket ) OnPong (socket * gws.Conn , payload []byte ) {}
func (c * WebSocket ) OnMessage (socket * gws.Conn , message * gws.Message ) {
socket .WriteMessage (message .Typ (), message .Bytes ())
message .Close ()
}
package main
import (
"github.com/gin-gonic/gin"
"github.com/lxzan/gws"
)
func main () {
app := gin .New ()
handler := new (WebSocket )
upgrader := gws .NewUpgrader (gws .WithEventHandler (handler ))
app .GET ("/connect" , func (ctx * gin.Context ) {
socket , err := upgrader .Accept (ctx .Writer , ctx .Request )
if err != nil {
return
}
upgrader .Listen (socket )
})
cert := "server.crt"
key := "server.key"
if err := app .RunTLS (":8443" , cert , key ); err != nil {
panic (err )
}
}
cd examples/testsuite
mkdir reports
docker run -it --rm \
-v ${PWD} /config:/config \
-v ${PWD} /reports:/reports \
crossbario/autobahn-testsuite \
wstest -m fuzzingclient -s /config/fuzzingclient.json