Documentation
¶
Overview ¶
Package socketio implements the Socket.IO v5 protocol layer on top of the engineio transport package.
Protocol reference: https://socket.io/docs/v4/socket-io-protocol/
Index ¶
- Variables
- type Adapter
- type AdapterFactory
- type BroadcastOperator
- type CORSConfig
- type Client
- func (c *Client) Close() error
- func (c *Client) ConnectNamespace(ctx context.Context, nsp string, data map[string]any) error
- func (c *Client) Connected(nsp string) bool
- func (c *Client) DisconnectNamespace(nsp string)
- func (c *Client) Emit(nsp, event string, args ...any) error
- func (c *Client) EmitWithAck(nsp, event string, cb func(args []any), args ...any) (int64, error)
- func (c *Client) ID(nsp string) string
- func (c *Client) OnConnect(nsp string, f func())
- func (c *Client) OnConnectError(nsp string, f func(err error))
- func (c *Client) OnDisconnect(nsp string, f func(reason string))
- func (c *Client) OnError(nsp string, f func(err error))
- func (c *Client) OnEvent(nsp, event string, f any)
- func (c *Client) SetLogger(l engineio.Logger)
- type Middleware
- type Options
- type Packet
- type PacketType
- type Server
- func (s *Server) BroadcastToNamespace(nsp, event string, args ...any)
- func (s *Server) BroadcastToRoom(nsp, room, event string, args ...any)
- func (s *Server) Close()
- func (s *Server) Engine() *engineio.Server
- func (s *Server) Namespace(nsp string) *namespace
- func (s *Server) OnAny(nsp string, f func(*Socket, string, []any))
- func (s *Server) OnConnect(nsp string, f func(*Socket) error)
- func (s *Server) OnDisconnect(nsp string, f func(*Socket, string))
- func (s *Server) OnError(nsp string, f func(*Socket, error))
- func (s *Server) OnEvent(nsp, event string, f any)
- func (s *Server) RegisterNamespace(nsp string) error
- func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Server) SetAdapterFactory(f AdapterFactory)
- func (s *Server) SetLogger(l engineio.Logger)
- func (s *Server) SetUnknownEventAck(nsp string, data []any)
- func (s *Server) ToExcept(nsp, room string, except []string, event string, args ...any) error
- func (s *Server) Use(nsp string, m Middleware)
- type ServerConfig
- type Socket
- func (s *Socket) Broadcast(event string, args ...any)
- func (s *Socket) BroadcastToRoom(room, event string, args ...any)
- func (s *Socket) Connected() bool
- func (s *Socket) Disconnect()
- func (s *Socket) Emit(event string, args ...any) error
- func (s *Socket) EmitWithAck(event string, cb func(args []any), args ...any) (int64, error)
- func (s *Socket) Engine() *engineio.Socket
- func (s *Socket) ID() string
- func (s *Socket) JoinRoom(room string)
- func (s *Socket) LeaveRoom(room string)
- func (s *Socket) Nsp() string
- func (s *Socket) RemoteAddr() string
- func (s *Socket) To(room string) *BroadcastOperator
- func (s *Socket) ToExcept(room string, except []string) *BroadcastOperator
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNamespaceNotConnected is returned when emitting on a namespace the // client is not connected to. It wraps ErrNotConnected so callers can // match either the namespace-level or the base sentinel with errors.Is. ErrNamespaceNotConnected = fmt.Errorf("%w: namespace not connected", ErrNotConnected) // ErrHandlerMismatch is returned when an event handler's signature does // not match the event's arguments. ErrHandlerMismatch = errors.New("socketio: handler signature mismatch") )
sentinel errors exposed to users.
var ( // ErrNotConnected is returned when emitting on a disconnected socket. ErrNotConnected = errors.New("socketio: socket is not connected") // ErrInvalidNamespace is returned when broadcasting to an unknown namespace. ErrInvalidNamespace = errors.New("socketio: invalid namespace") )
sentinel errors exposed to users.
var NopLogger engineio.Logger = slogLogger{/* contains filtered or unexported fields */}
NopLogger discards all log output.
Functions ¶
This section is empty.
Types ¶
type Adapter ¶ added in v1.1.0
type Adapter interface {
// AddSocket registers an id and automatically joins it to a room named
// after the id itself.
AddSocket(id string)
// RemoveSocket removes the id from the adapter and from every room.
RemoveSocket(id string)
// AddToRoom joins an id to a room.
AddToRoom(room, id string)
// RemoveFromRoom leaves an id from a room.
RemoveFromRoom(room, id string)
// Broadcast invokes deliver once per target id. An empty room means the
// whole namespace; ids listed in except are skipped; an unknown room
// delivers to nobody. deliver is called outside any adapter lock.
Broadcast(room string, except []string, deliver func(id string))
// Sockets returns the current socket ids. The adapter is authoritative
// for membership.
Sockets() []string
// SocketsCount returns the number of current socket ids.
SocketsCount() int
// Close releases adapter resources. It must be idempotent.
Close() error
}
Adapter is the pluggable room and membership backend of a Socket.IO namespace. It is id-driven and knows nothing about *Socket: the namespace resolves ids to sockets when delivering. A custom Adapter (e.g. one backed by Redis) can be injected by the server; NewMemoryAdapter is the default in-process implementation.
Lock discipline: Broadcast must invoke deliver only after it released its internal lock — the namespace's addSocket path takes the namespace lock and then calls adapter methods, so an adapter that delivers under its own lock deadlocks with that path.
func NewMemoryAdapter ¶ added in v1.1.0
func NewMemoryAdapter() Adapter
NewMemoryAdapter returns the default in-process Adapter: an id set, a room-to-ids map and a single RWMutex protecting both.
type AdapterFactory ¶ added in v1.1.0
AdapterFactory creates the Adapter for a namespace, typically keyed by the namespace name so each namespace gets its own state.
type BroadcastOperator ¶
type BroadcastOperator struct {
// contains filtered or unexported fields
}
BroadcastOperator targets a subset of a namespace for broadcasting.
func (*BroadcastOperator) Emit ¶
func (b *BroadcastOperator) Emit(event string, args ...any)
Emit sends the event to the targeted sockets.
func (*BroadcastOperator) Except ¶ added in v1.1.0
func (b *BroadcastOperator) Except(ids ...string) *BroadcastOperator
Except returns a new operator that also excludes the listed ids, chainable before Emit. The receiver is left unchanged.
type CORSConfig ¶ added in v1.1.0
CORSConfig describes the cross-origin access rules of a server. AllowAll permits every origin; AllowedOrigins lists the exact origins that are permitted.
func AllowAll ¶ added in v1.1.0
func AllowAll() *CORSConfig
AllowAll returns a CORSConfig that permits every origin.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a Socket.IO client. It maintains one Engine.IO connection multiplexing any number of namespaces, and mirrors the official socket.io-client behaviour for events, acknowledgements, binary payloads and reconnection.
func Dial ¶
Dial connects to a Socket.IO server. It blocks until the root namespace CONNECT completes or ctx is cancelled.
Example ¶
ExampleDial demonstrates a full Socket.IO round trip: start a server, connect a client, emit an event and receive the acknowledgement. The handler's return value becomes the ack payload.
package main
import (
"context"
"fmt"
"log"
"net/http/httptest"
"time"
"github.com/kingecg/gosocketio/socketio"
)
func main() {
srv := socketio.NewServer(nil)
srv.OnEvent("/", "ping", func(s *socketio.Socket) string {
return "pong"
})
httpSrv := httptest.NewServer(srv)
defer httpSrv.Close()
c, err := socketio.Dial(context.Background(), httpSrv.URL+"/socket.io/", nil)
if err != nil {
log.Fatal(err)
}
defer c.Close()
ack := make(chan []any, 1)
if _, err := c.EmitWithAck("/", "ping", func(args []any) {
ack <- args
}); err != nil {
log.Fatal(err)
}
select {
case args := <-ack:
fmt.Printf("ack: %v\n", args)
case <-time.After(5 * time.Second):
log.Fatal("timed out waiting for ack")
}
}
Output: ack: [pong]
func (*Client) Close ¶
Close terminates the session. Pending acknowledgements are dropped and every connected namespace reports a disconnect with reason "io client disconnect".
func (*Client) ConnectNamespace ¶
ConnectNamespace connects an additional namespace, blocking until the CONNECT acknowledgement arrives or ctx is cancelled.
func (*Client) DisconnectNamespace ¶
DisconnectNamespace disconnects a namespace, notifying the server.
func (*Client) EmitWithAck ¶
EmitWithAck sends an event that requires an acknowledgement. cb is invoked with the server's reply once it arrives.
func (*Client) OnConnect ¶
OnConnect registers a handler invoked when a namespace connection completes. It fires again after each successful reconnection.
func (*Client) OnConnectError ¶
OnConnectError registers a handler invoked when a namespace connection is rejected, carrying the error reported by the server.
func (*Client) OnDisconnect ¶
OnDisconnect registers a handler invoked when a namespace disconnects, carrying the Socket.IO disconnect reason.
func (*Client) OnError ¶ added in v1.1.0
OnError registers a handler invoked when an event handler fails to dispatch (for example an argument that cannot be decoded into the handler's parameter type), carrying the dispatch error. It is distinct from OnConnectError and never fires for connect, disconnect or connect_error paths.
type Middleware ¶
Middleware is invoked with the CONNECT payload before a connection to the namespace is accepted. Returning a non-nil error rejects the connection with a CONNECT_ERROR packet carrying the error message.
type Options ¶
type Options struct {
// Transports overrides the Engine.IO transports.
Transports []string
// Engine passes extra options to engineio.Dial.
Engine *engineio.Options
// Auth is sent as the root namespace CONNECT payload.
Auth map[string]any
// Reconnection enables automatic reconnection after an unexpected
// close of the underlying transport.
Reconnection bool
// ReconnectionAttempts limits consecutive reconnection attempts. Zero
// (the default) means retry forever.
ReconnectionAttempts int
// ReconnectionDelay is the base delay before the first reconnection
// attempt. Default: 1s.
ReconnectionDelay time.Duration
// ReconnectionDelayMax caps the exponentially backed-off delay.
// Default: 5s.
ReconnectionDelayMax time.Duration
// RandomizationFactor in [0,1] jitters the delay so simultaneous
// clients do not reconnect in lockstep. Default: 0.5.
RandomizationFactor float64
// Timeout bounds each reconnection attempt (Engine.IO dial plus all
// namespace CONNECTs). Default: 10s.
Timeout time.Duration
}
Options configures a Socket.IO client session.
type Packet ¶
type Packet struct {
Type PacketType
Nsp string
ID int64
Data any
// Attachments is the number of binary buffers following a binary
// packet. It is only meaningful for BinaryEvent/BinaryAck packets.
Attachments int
}
Packet is a Socket.IO packet. Data holds the decoded JSON payload:
- Connect / ConnectError: map[string]any (or nil)
- Event: []any ([eventName, args...])
- Ack: []any ([args...])
A negative ID means the packet carries no acknowledgement id.
type PacketType ¶
type PacketType byte
PacketType is the Socket.IO packet type.
const ( Connect PacketType = '0' Disconnect PacketType = '1' Event PacketType = '2' Ack PacketType = '3' ConnectError PacketType = '4' BinaryEvent PacketType = '5' BinaryAck PacketType = '6' )
Socket.IO packet types.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a Socket.IO server layered on top of an Engine.IO server. It implements http.Handler.
func NewServer ¶
NewServer creates a Socket.IO server with its own Engine.IO server. opts customizes the underlying Engine.IO layer.
func NewServerWithConfig ¶ added in v1.1.0
func NewServerWithConfig(cfg *ServerConfig) *Server
NewServerWithConfig creates a Socket.IO server from a ServerConfig. A nil config or a zero-value config behaves exactly like NewServer(nil).
func (*Server) BroadcastToNamespace ¶
BroadcastToNamespace sends an event to every socket in a namespace.
func (*Server) BroadcastToRoom ¶
BroadcastToRoom sends an event to every socket in a namespace's room.
Example ¶
ExampleServer_BroadcastToRoom demonstrates room-scoped broadcasting: every socket that joined the room receives the message (including the sender), while sockets outside the room do not.
package main
import (
"context"
"fmt"
"log"
"net/http/httptest"
"sort"
"time"
"github.com/kingecg/gosocketio/socketio"
)
func main() {
srv := socketio.NewServer(nil)
srv.OnEvent("/", "join", func(s *socketio.Socket, room string) string {
s.JoinRoom(room)
return "joined"
})
srv.OnEvent("/", "room message", func(s *socketio.Socket, text string) {
srv.BroadcastToRoom("/", "lobby", "room message", text)
})
httpSrv := httptest.NewServer(srv)
defer httpSrv.Close()
connect := func() *socketio.Client {
c, err := socketio.Dial(context.Background(), httpSrv.URL+"/socket.io/", nil)
if err != nil {
log.Fatal(err)
}
return c
}
alice := connect()
defer alice.Close()
bob := connect()
defer bob.Close()
// The server acknowledges each join, so membership is guaranteed before
// the broadcast happens.
join := func(c *socketio.Client) {
done := make(chan []any, 1)
if _, err := c.EmitWithAck("/", "join", func(args []any) { done <- args }, "lobby"); err != nil {
log.Fatal(err)
}
select {
case <-done:
case <-time.After(5 * time.Second):
log.Fatal("timed out waiting for join ack")
}
}
join(alice)
join(bob)
// Carol connects but never joins the room.
carol := connect()
defer carol.Close()
carolGot := make(chan struct{})
carol.OnEvent("/", "room message", func(text string) {
close(carolGot)
})
got := make(chan string, 2)
alice.OnEvent("/", "room message", func(text string) { got <- "alice: " + text })
bob.OnEvent("/", "room message", func(text string) { got <- "bob: " + text })
if err := alice.Emit("/", "room message", "hello lobby"); err != nil {
log.Fatal(err)
}
msgs := make([]string, 0, 2)
for i := 0; i < 2; i++ {
select {
case m := <-got:
msgs = append(msgs, m)
case <-time.After(5 * time.Second):
log.Fatal("timed out waiting for room broadcast")
}
}
// Sort ensures deterministic output regardless of receive order.
sort.Strings(msgs)
for _, m := range msgs {
fmt.Println(m)
}
select {
case <-carolGot:
log.Fatal("carol received a message despite not being in the room")
default:
fmt.Println("carol: no message (not in the room)")
}
}
Output: alice: hello lobby bob: hello lobby carol: no message (not in the room)
func (*Server) Close ¶
func (s *Server) Close()
Close closes all sessions and releases every namespace's adapter.
func (*Server) OnAny ¶ added in v1.1.0
OnAny registers a handler invoked for every event a client emits in the namespace, before the named event handlers dispatch. The handler receives the socket, the event name and the decoded arguments. It fires for every EVENT packet — including events with no registered handler — and never for connect, disconnect, connect_error or acknowledgement packets.
func (*Server) OnConnect ¶
OnConnect registers a handler invoked when a client connects to a namespace. Returning an error rejects the connection with a CONNECT_ERROR packet.
func (*Server) OnDisconnect ¶
OnDisconnect registers a handler invoked when a socket leaves a namespace.
func (*Server) OnError ¶ added in v1.1.0
OnError registers a handler invoked when an event handler fails to dispatch (for example an argument that cannot be decoded into the handler's parameter type), carrying the socket and the dispatch error. It never fires for connect, disconnect or connect_error paths.
func (*Server) OnEvent ¶
OnEvent registers a handler for an event within a namespace.
Example ¶
ExampleServer_OnEvent demonstrates a server-side event handler receiving typed arguments. []byte arguments travel as binary attachments and are reconstructed before the handler is invoked.
package main
import (
"context"
"fmt"
"log"
"net/http/httptest"
"time"
"github.com/kingecg/gosocketio/socketio"
)
func main() {
srv := socketio.NewServer(nil)
srv.OnEvent("/", "download", func(s *socketio.Socket, name string, data []byte) {
fmt.Printf("server received %q (%d bytes)\n", name, len(data))
s.Emit("file", name, data)
})
httpSrv := httptest.NewServer(srv)
defer httpSrv.Close()
c, err := socketio.Dial(context.Background(), httpSrv.URL+"/socket.io/", nil)
if err != nil {
log.Fatal(err)
}
defer c.Close()
got := make(chan struct{})
c.OnEvent("/", "file", func(name string, data []byte) {
fmt.Printf("client received %q (%d bytes)\n", name, len(data))
close(got)
})
if err := c.Emit("/", "download", "logo.png", []byte{0x89, 0x50, 0x4e, 0x47}); err != nil {
log.Fatal(err)
}
select {
case <-got:
case <-time.After(5 * time.Second):
log.Fatal("timed out waiting for binary echo")
}
}
Output: server received "logo.png" (4 bytes) client received "logo.png" (4 bytes)
func (*Server) RegisterNamespace ¶ added in v1.1.0
RegisterNamespace explicitly pre-registers a namespace so handlers can be attached before any client connects. It is idempotent: registering an already-created namespace returns nil. The namespace must start with "/" and be longer than one character; the empty string and "/" are rejected with a descriptive error. Implicit on-demand creation of unregistered namespaces is unchanged.
func (*Server) ServeHTTP ¶
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler. When the server has a configured path, requests outside that prefix are rejected with 404 before delegating to the Engine.IO server. When CORS is configured, preflight and origin checks run after the path guard and before the engine.
func (*Server) SetAdapterFactory ¶ added in v1.1.0
func (s *Server) SetAdapterFactory(f AdapterFactory)
SetAdapterFactory sets the factory used to create the Adapter of namespaces created after this call. It is ignored when a factory was already configured through ServerConfig.Adapter. A nil factory selects the in-memory adapter. Namespaces created before this call keep their existing adapter.
func (*Server) SetUnknownEventAck ¶ added in v1.2.0
SetUnknownEventAck sets the acknowledgement payload returned for EVENT packets that carry an ack id but match no registered handler in the namespace. nil (the default) keeps the historical behaviour of acknowledging with an empty payload.
func (*Server) ToExcept ¶ added in v1.1.0
ToExcept sends an event to every socket in a namespace's room except the listed ids. An empty except list delivers to every member, matching BroadcastToRoom. It returns an error when the namespace does not exist.
func (*Server) Use ¶
func (s *Server) Use(nsp string, m Middleware)
Use registers a connection middleware for a namespace.
Example ¶
ExampleServer_Use demonstrates a connection middleware that validates the CONNECT payload (for example an auth token) and rejects unauthorized clients with a CONNECT_ERROR.
package main
import (
"context"
"errors"
"fmt"
"log"
"net/http/httptest"
"github.com/kingecg/gosocketio/socketio"
)
func main() {
srv := socketio.NewServer(nil)
srv.Use("/", func(s *socketio.Socket, data map[string]any) error {
if token, _ := data["token"].(string); token != "secret" {
return errors.New("unauthorized")
}
return nil
})
httpSrv := httptest.NewServer(srv)
defer httpSrv.Close()
good, err := socketio.Dial(context.Background(), httpSrv.URL+"/socket.io/", &socketio.Options{
Auth: map[string]any{"token": "secret"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println("authorized: connected")
good.Close()
if _, err := socketio.Dial(context.Background(), httpSrv.URL+"/socket.io/", &socketio.Options{
Auth: map[string]any{"token": "wrong"},
}); err != nil {
fmt.Println("unauthorized:", err)
}
}
Output: authorized: connected unauthorized: unauthorized
type ServerConfig ¶ added in v1.1.0
type ServerConfig struct {
// Engine customizes the underlying Engine.IO server. nil selects the
// Engine.IO defaults.
Engine *engineio.Options
// Path restricts requests to those whose URL path has this prefix. The
// empty string disables the guard, matching the historical behavior of
// NewServer.
Path string
// CORS configures cross-origin access. nil disables CORS handling; the
// behavior itself is implemented by the CORS layer.
CORS *CORSConfig
// Adapter creates the room and membership backend for each namespace. nil
// selects the in-memory adapter.
Adapter AdapterFactory
}
ServerConfig configures a Socket.IO server.
type Socket ¶
type Socket struct {
// contains filtered or unexported fields
}
Socket is a single Socket.IO connection to a namespace. It is created when a client sends a CONNECT packet for the namespace and stays valid until the client or server disconnects that namespace.
func (*Socket) Broadcast ¶
Broadcast sends an event to every socket in the namespace except this one.
func (*Socket) BroadcastToRoom ¶
BroadcastToRoom sends an event to every socket in room except this one.
func (*Socket) Disconnect ¶
func (s *Socket) Disconnect()
Disconnect disconnects this namespace socket, notifying the client.
func (*Socket) EmitWithAck ¶
EmitWithAck sends an event that requires an acknowledgement. cb is invoked with the client's reply once it arrives.
func (*Socket) RemoteAddr ¶
RemoteAddr returns the remote address of the underlying connection.
func (*Socket) To ¶
func (s *Socket) To(room string) *BroadcastOperator
To returns a broadcast operator targeting a single room.