Skip to main content
Version: v3_websocket_v1.x.x

WebSocket Event

Plain WebSocket event helper for Fiber, built on top of github.com/gofiber/contrib/v3/websocket.

This package is for applications that want the legacy event-bus behavior over ordinary WebSocket clients. It does not implement the Engine.IO or Socket.IO protocol. Use github.com/gofiber/contrib/v3/socketio when you need compatibility with the official socket.io-client package.

If your application used the older socketio package as a plain WebSocket event bus, migrate the import to github.com/gofiber/contrib/v3/websocket/event. The API intentionally stays close to that legacy event helper, while the socketio package is reserved for the Socket.IO protocol.

Compatible with Fiber v3.

Install

go get -u github.com/gofiber/contrib/v3/websocket

The event helper is the event subpackage of that module:

import "github.com/gofiber/contrib/v3/websocket/event"

Signatures

Create a handler:

func New(callback func(kws *event.Websocket), config ...websocket.Config) fiber.Handler
func NewWithConfig(callback func(kws *event.Websocket), eventCfg event.Config, wsConfig ...websocket.Config) fiber.Handler

Register listeners. Package-level On is process-global; the (*Websocket) method form is scoped to a single connection (see Listeners):

type EventCallback func(payload *event.EventPayload)

func On(name string, callback EventCallback) // global: fires for every connection
func Off(name string) // removes global listeners for name

func (kws *Websocket) On(name string, callback EventCallback) // this connection only
func (kws *Websocket) Off(name string) // remove this connection's listeners

Send messages. The (*Websocket) method forms operate on / from a specific connection and fire EventError on failure; the package-level forms address connections in the global pool and do not fire EventError (see Sending messages):

// Method forms (fire EventError on failure).
func (kws *Websocket) Emit(message []byte, mType ...int)
func (kws *Websocket) EmitTo(uuid string, message []byte, mType ...int) error
func (kws *Websocket) EmitToList(uuids []string, message []byte, mType ...int)
func (kws *Websocket) Broadcast(message []byte, except bool, mType ...int)
func (kws *Websocket) Fire(name string, data []byte)

// Package forms (do not fire EventError).
func EmitTo(uuid string, message []byte, mType ...int) error
func EmitToList(uuids []string, message []byte, mType ...int)
func Broadcast(message []byte, mType ...int)
func Fire(name string, data []byte)

Connection identity and attributes:

func (kws *Websocket) GetUUID() string
func (kws *Websocket) SetUUID(uuid string) error // returns ErrorUUIDDuplication on conflict
func (kws *Websocket) SetAttribute(key string, value interface{})
func (kws *Websocket) GetAttribute(key string) interface{}

Graceful shutdown:

func Drain()
func IsDraining() bool
func CloseAll(ctx context.Context, code int, reason string) error

Listeners

On registers a process-global listener: the callback fires for the given event on every connection created by New / NewWithConfig, regardless of route or Config. Listeners are additive and stay registered until removed with Off. This matches the legacy socketio event bus.

event.On(event.EventMessage, func(ep *event.EventPayload) { /* ... */ })
event.Off(event.EventMessage) // remove again, e.g. on reconfiguration or in tests

For listeners that should fire for a single connection only, use the (*Websocket).On method (typically from the New callback). Per-connection listeners fire in addition to the global ones and are discarded automatically when the connection disconnects:

app.Get("/ws/:id", event.New(func(kws *event.Websocket) {
kws.On(event.EventMessage, func(ep *event.EventPayload) {
// only fires for this connection
})
}))

Sending messages

There are two flavors of EmitTo / EmitToList / Broadcast, and the difference is easy to miss:

FormTargetsOn failure
(*Websocket).EmitToa UUID in the poolfires EventError on kws and returns the error
(*Websocket).EmitToLista list of UUIDsfires EventError on kws per failed UUID
(*Websocket).Broadcastall connections (except skips kws itself)fires EventError on kws per failed UUID
EmitTo (package)a UUID in the poolreturns the error, does not fire EventError
EmitToList (package)a list of UUIDssilently ignores per-UUID errors
Broadcast (package)all connectionsfire-and-forget, no error feedback

Use the method forms when you want delivery failures surfaced as EventError events; use the package forms for fire-and-forget fan-out. Emit enqueues the message on the connection's outbound queue, which a dedicated goroutine drains in order; if the queue is full it blocks until a slot frees up or the connection closes.

Configuration

Per-instance tuning via event.Config passed to NewWithConfig. Zero values fall back to the matching package-level var, which itself falls back to the hard default.

Config fieldDefaultDescription
PingInterval1sInterval between server-originated Ping frames. Must be less than any upstream proxy or load balancer idle timeout.
ReadIdleTimeout3 * PingIntervalMaximum silence before the read deadline fires and the connection is disconnected.
WriteTimeout10sBounds a single WriteMessage / WriteControl call.
MaxMessageSize1 MiBInbound frame size limit. Set to math.MaxInt64 to opt out.
SendQueueSize100Per-connection outbound message queue capacity.
MaxSendRetry5Max retries for transient socket write readiness issues.
RetrySendTimeout20msBackoff between retries while the connection is not ready.
RecoverHandlernilCalled on a panic inside a user On callback. If nil, panics are recovered silently.

The legacy package-level vars (PongTimeout, RetrySendTimeout, MaxSendRetry, SendQueueSize, ReadTimeout) are still read once per connection at upgrade time for backwards compatibility, but mutating them after a connection is established has no effect on running goroutines. Prefer NewWithConfig for new code.

Thread safety

On, Off, Fire, the package-level EmitTo / EmitToList / Broadcast, and the connection pool are safe for concurrent use. A single *Websocket is safe to use from multiple goroutines: reads and writes are serialized through a per-connection mutex and an outbound send queue. Listener callbacks run on the helper's goroutines, so a callback that blocks holds up event delivery for that connection; offload long work to your own goroutine.

Graceful Shutdown

The helper keeps an in-process pool of active connections. Use event.Drain and event.CloseAll together with a Fiber shutdown hook so clients receive a clean 1001 Going Away close frame instead of an abrupt TCP reset:

app.Hooks().OnShutdown(func() error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
event.Drain()
return event.CloseAll(ctx, websocket.CloseGoingAway, "server shutting down")
})

If ctx expires before every goroutine exits, CloseAll force-closes the remaining underlying connections and returns ctx.Err().

Drain only flips the draining flag; it does not refuse new connections by itself. Gate the upgrade route on IsDraining to stop accepting clients during shutdown:

app.Use("/ws", func(c fiber.Ctx) error {
if event.IsDraining() {
return fiber.NewError(fiber.StatusServiceUnavailable, "shutting down")
}
if websocket.IsWebSocketUpgrade(c) {
return c.Next()
}
return fiber.ErrUpgradeRequired
})

Example

package main

import (
"log"

"github.com/gofiber/contrib/v3/websocket"
"github.com/gofiber/contrib/v3/websocket/event"
"github.com/gofiber/fiber/v3"
)

func main() {
app := fiber.New()

app.Use("/ws", func(c fiber.Ctx) error {
if websocket.IsWebSocketUpgrade(c) {
return c.Next()
}
return fiber.ErrUpgradeRequired
})

event.On(event.EventMessage, func(ep *event.EventPayload) {
ep.Kws.Emit([]byte("echo: "+string(ep.Data)), event.TextMessage)
})

app.Get("/ws/:id", event.New(func(kws *event.Websocket) {
// kws.Params / kws.Locals / kws.Query / kws.Cookies wrap the Fiber
// request context captured at upgrade time.
kws.SetAttribute("user_id", kws.Params("id"))
}))

log.Fatal(app.Listen(":3000"))
}

Custom events

Event names are arbitrary strings. Register a listener with On and trigger it with Fire (on one connection) or the package-level Fire (on all):

event.On("notify", func(ep *event.EventPayload) {
log.Printf("notify %s: %s", ep.SocketUUID, ep.Data)
})

// from a connection:
kws.Fire("notify", []byte("hello"))

// to every active connection:
event.Fire("notify", []byte("broadcast"))

Supported Events

ConstEventDescription
EventMessagemessageFired when a text or binary message is received.
EventPingpingFired when a WebSocket ping control frame is received.
EventPongpongFired when a WebSocket pong control frame is received.
EventDisconnectdisconnectFired when the connection is closed. On an error close, EventError fires too.
EventConnectconnectFired after the New callback runs, before the read loop starts.
EventClosecloseFired when the server actively closes the connection.
EventErrorerrorFired on a failed EmitTo, a dropped outbound message, or an error-driven disconnect.

Event Payload

FieldTypeDescription
Kws*event.WebsocketThe connection object.
NamestringThe event name.
SocketUUIDstringUnique connection UUID.
SocketAttributesmap[string]anySnapshot of optional connection attributes.
ErrorerrorOptional error for disconnect and error events.
Data[]byteData used on message, custom, and error events.