Documentation
¶
Overview ¶
Package socketio is a pure-Go port of the Node.js Socket.IO server. It implements the Engine.IO v4 transport layer (HTTP long-polling and WebSocket, with the polling→websocket upgrade) and the Socket.IO v5 protocol (namespaces, rooms, events, and acknowledgements), exposing an API that mirrors the original JavaScript library so that idioms such as io.on("connection"), socket.join(room), and io.to(room).emit(...) translate almost line for line:
io := socketio.New()
io.OnConnection(func(s *socketio.Socket) {
s.On("chat", func(args []any) []any {
io.To("room1").Emit("chat", args...)
return nil
})
s.Join("room1")
})
http.Handle("/socket.io/", io)
http.ListenAndServe(":3000", nil)
Reach for this package when you want real-time, bidirectional, event-based communication between a Go backend and Socket.IO clients (browsers, mobile apps, or the companion client package) without pulling in cgo or third-party dependencies — the entire stack, down to the RFC 6455 WebSocket framing, is built on the standard library. A *Server is an http.Handler, so it mounts on any net/http server or router: use ServeHTTP directly, Attach it to a *http.ServeMux, or wrap an existing handler with Handler to coexist with a REST/Express-style application on the same port.
Internally each connected client owns one Engine.IO session (a conn), identified by a session id (sid). A session begins over HTTP long-polling and is transparently upgraded to WebSocket via the Engine.IO probe handshake; the engineio subpackage encodes the transport frames and the polling payload, while this package encodes the Socket.IO layer on top — CONNECT, EVENT, ACK, DISCONNECT, and their binary variants. Events carry JSON arguments; any []byte in a payload is transmitted out-of-band as a BINARY_EVENT with placeholder markers (see binary.go). A single session may be attached to several Namespaces at once, each of which multiplexes its own sockets, rooms, and connection middleware over the shared transport.
The concurrency and delivery semantics follow from that design. The server sends periodic heartbeat pings and disconnects a session whose pong is overdue by more than PingInterval+PingTimeout. Inbound events are dispatched on their own goroutine so a handler may block on an acknowledgement without stalling the read loop, which means handlers for the same socket can run concurrently and must guard any shared state; ordering of delivery to the wire is preserved, but ordering between independently dispatched handlers is not. Socket.Emit targets one client and returns an error, whereas the broadcast forms (Server.Emit, Namespace.Emit, and BroadcastOperator.Emit) fan out to many recipients and deliberately do not surface a per-socket error. Rooms are managed by a pluggable Adapter; the default keeps all membership in process.
Parity with the Node reference implementation is close but not total. The wire protocols are compatible, so this server interoperates with the official JavaScript client and this module's client package. Room broadcasting, acknowledgements, connection middleware (Use), per-socket data (Set/Get), and multi-node scale-out (via SetBroadcaster and the redis subpackage) are all supported. Differences reflect Go idioms and scope: handlers use typed func([]any) []any signatures rather than variadic JS callbacks, there is no built-in adapter persistence beyond what a Broadcaster provides, and features tied to the Node runtime (such as the admin UI or the msgpack parser) are out of scope. See COMPATIBILITY.md in the repository for the authoritative matrix.
Index ¶
- Constants
- Variables
- func AllowedOrigins(origins ...string) func(*http.Request) bool
- func IsReservedEvent(name string) bool
- func Reconstruct(data any, buffers [][]byte) (any, error)
- func ReservedEvents() []string
- func ValidateEventName(name string) error
- type Adapter
- type BroadcastOperator
- func (b *BroadcastOperator) Compress(on bool) *BroadcastOperator
- func (b *BroadcastOperator) Emit(event string, args ...any)
- func (b *BroadcastOperator) Except(socketID string) *BroadcastOperator
- func (b *BroadcastOperator) ExceptRoom(rooms ...string) *BroadcastOperator
- func (b *BroadcastOperator) In(room string) *BroadcastOperator
- func (b *BroadcastOperator) Local() *BroadcastOperator
- func (b *BroadcastOperator) Timeout(d time.Duration) *BroadcastOperator
- func (b *BroadcastOperator) To(room string) *BroadcastOperator
- func (b *BroadcastOperator) Volatile() *BroadcastOperator
- type BroadcastTarget
- type Broadcaster
- type Decoder
- type Emitter
- type Encoder
- type EventHandler
- type Handshake
- type Namespace
- func (ns *Namespace) DisconnectSockets(closeTransport bool)
- func (ns *Namespace) Emit(event string, args ...any)
- func (ns *Namespace) Except(room string) *BroadcastOperator
- func (ns *Namespace) FetchSockets() []*Socket
- func (ns *Namespace) In(room string) *BroadcastOperator
- func (ns *Namespace) Local() *BroadcastOperator
- func (ns *Namespace) Name() string
- func (ns *Namespace) OnConnection(fn func(*Socket)) *Namespace
- func (ns *Namespace) SetAdapter(a Adapter) *Namespace
- func (ns *Namespace) Sockets() []*Socket
- func (ns *Namespace) SocketsInRoom(room string) []*Socket
- func (ns *Namespace) SocketsJoin(rooms ...string)
- func (ns *Namespace) SocketsLeave(rooms ...string)
- func (ns *Namespace) To(room string) *BroadcastOperator
- func (ns *Namespace) Use(fn func(socket *Socket, next func(err error))) *Namespace
- type Options
- type Packet
- type PacketType
- type RoomTargeter
- type Server
- func (s *Server) Attach(mux *http.ServeMux)
- func (s *Server) Close()
- func (s *Server) DisconnectSockets(closeTransport bool)
- func (s *Server) Emit(event string, args ...any)
- func (s *Server) Except(room string) *BroadcastOperator
- func (s *Server) FetchSockets() []*Socket
- func (s *Server) Handler(next http.Handler) http.Handler
- func (s *Server) In(room string) *BroadcastOperator
- func (s *Server) Local() *BroadcastOperator
- func (s *Server) Of(name string) *Namespace
- func (s *Server) OnConnection(fn func(*Socket))
- func (s *Server) OnServerEvent(event string, handler func(args []any)) *Server
- func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Server) ServerSideEmit(event string, args ...any)
- func (s *Server) SetBroadcaster(b Broadcaster) *Server
- func (s *Server) Sockets() []*Socket
- func (s *Server) SocketsJoin(rooms ...string)
- func (s *Server) SocketsLeave(rooms ...string)
- func (s *Server) To(room string) *BroadcastOperator
- func (s *Server) Use(fn func(socket *Socket, next func(err error))) *Server
- type Socket
- func (s *Socket) Auth() any
- func (s *Socket) Broadcast() *BroadcastOperator
- func (s *Socket) Data() map[string]any
- func (s *Socket) Delete(key string) *Socket
- func (s *Socket) Disconnect(closeTransport bool)
- func (s *Socket) Emit(event string, args ...any) error
- func (s *Socket) EmitAck(event string, timeout time.Duration, args ...any) ([]any, error)
- func (s *Socket) EmitWithAck(event string, ackFn func(args []any), args ...any) error
- func (s *Socket) Except(room string) *BroadcastOperator
- func (s *Socket) Get(key string) (any, bool)
- func (s *Socket) GetString(key string) string
- func (s *Socket) Handshake() *Handshake
- func (s *Socket) ID() string
- func (s *Socket) In(room string) *BroadcastOperator
- func (s *Socket) Join(rooms ...string) *Socket
- func (s *Socket) Leave(rooms ...string) *Socket
- func (s *Socket) ListenersAny() []func(event string, args []any)
- func (s *Socket) Namespace() *Namespace
- func (s *Socket) Off(event string) *Socket
- func (s *Socket) OffAny() *Socket
- func (s *Socket) On(event string, handler EventHandler) *Socket
- func (s *Socket) OnAny(fn func(event string, args []any)) *Socket
- func (s *Socket) OnDisconnect(fn func(reason string)) *Socket
- func (s *Socket) OnDisconnecting(fn func(reason string)) *Socket
- func (s *Socket) PendingAcks() int
- func (s *Socket) PrependAny(fn func(event string, args []any)) *Socket
- func (s *Socket) Rooms() []string
- func (s *Socket) Set(key string, value any) *Socket
- func (s *Socket) To(room string) *BroadcastOperator
Examples ¶
Constants ¶
const DefaultMaxSessions = 65536
DefaultMaxSessions is the concurrent-session cap applied when Options.MaxSessions is zero. It is deliberately far above what a single process is expected to serve: it exists to bound a handshake flood, not to throttle legitimate traffic.
const DefaultPath = "/socket.io/"
DefaultPath is the HTTP path Socket.IO serves from.
const MaxAttachments = 1024
MaxAttachments bounds the number of binary buffers a single BINARY_EVENT or BINARY_ACK packet may declare in its "<n>-" wire prefix. The count is attacker-controlled — it arrives on the wire before any buffer does — so it is validated at decode time rather than trusted by the code that pre-allocates per-attachment storage. A packet declaring more than this is rejected with ErrInvalidPacket. The limit is far above any realistic payload (socket.io's own encoder emits one attachment per []byte argument).
const ProtocolVersion = 5
ProtocolVersion is the Socket.IO protocol revision implemented here (v5, which rides on Engine.IO v4).
Variables ¶
var ErrAckTimeout = errors.New("socketio: ack timeout")
ErrAckTimeout is returned by EmitAck when the client does not acknowledge the event within the supplied timeout.
var ErrEmptyEvent = errors.New("socketio: empty event name")
ErrEmptyEvent indicates an empty event name.
var ErrInvalidPacket = errors.New("socketio: invalid packet")
ErrInvalidPacket indicates a malformed Socket.IO packet.
var ErrReservedEvent = errors.New("socketio: reserved event name")
ErrReservedEvent indicates an attempt to use a reserved event name for an application event.
var ErrTooManyAttachments = fmt.Errorf("%w: too many binary attachments (max %d)", ErrInvalidPacket, MaxAttachments)
ErrTooManyAttachments indicates a BINARY_EVENT/BINARY_ACK packet declaring more than MaxAttachments binary buffers. It wraps ErrInvalidPacket so callers that only care about malformed input can keep testing for that.
Functions ¶
func AllowedOrigins ¶ added in v0.5.0
AllowedOrigins builds an Options.CheckOrigin function that admits only the listed origins. It is the Go equivalent of configuring socket.io's `cors: { origin: [...] }`, and exists because the zero value of Options accepts every origin:
io := socketio.New(socketio.Options{
CheckOrigin: socketio.AllowedOrigins("https://app.example.com"),
})
Comparison is on the exact Origin header value (scheme, host and port), case-insensitively for the scheme and host as browsers always send them lowercased. A request with no Origin header is allowed: only browsers send one, and the same-origin policy — the thing an allowlist protects — does not apply to a non-browser client, which could set any value it liked anyway.
Setting CheckOrigin matters most for the WebSocket transport. A WebSocket handshake is *not* subject to the same-origin policy: any page on any site can open a socket to this server and, if the browser attaches cookies, act with the visitor's authority ("cross-site WebSocket hijacking"). Unlike the polling transport, no CORS preflight stands in the way, so the Origin check is the only defense. Pass "*" to allow everything explicitly (the same as leaving CheckOrigin nil, but self-documenting).
Example ¶
ExampleAllowedOrigins locks a server down to the origins a browser app is actually served from. This matters most for the WebSocket transport: a WebSocket handshake is an ordinary GET that the same-origin policy does not restrict and that triggers no CORS preflight, so without an Origin check any page a user visits can open an authenticated socket to this server — cross-site WebSocket hijacking. Setting CheckOrigin also switches on Access-Control-Allow-Credentials for the polling transport, which is withheld while every origin is accepted.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
socketio "github.com/malcolmston/socketio"
)
func main() {
io := socketio.New(socketio.Options{
CheckOrigin: socketio.AllowedOrigins(
"https://app.example.com",
"https://admin.example.com",
),
})
defer io.Close()
ts := httptest.NewServer(io)
defer ts.Close()
probe := func(origin string) int {
req, _ := http.NewRequest(http.MethodGet, ts.URL+"/socket.io/?EIO=4&transport=polling", nil)
req.Header.Set("Origin", origin)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return 0
}
resp.Body.Close()
return resp.StatusCode
}
fmt.Println("app.example.com ->", probe("https://app.example.com"))
fmt.Println("evil.example ->", probe("https://evil.example"))
}
Output: app.example.com -> 200 evil.example -> 403
func IsReservedEvent ¶ added in v0.3.0
IsReservedEvent reports whether name is a Socket.IO reserved event name (such as "connect", "disconnect", or "connect_error"). Reserved names are emitted by the library itself and must not be used for application events, mirroring the RESERVED_EVENTS guard in the JavaScript server.
func Reconstruct ¶
Reconstruct walks a payload replacing {"_placeholder":true,"num":N} markers with the matching binary buffer. It is exported for client implementations that reassemble incoming binary packets.
Every placeholder index is validated against the buffer list: a marker whose num is negative or points past the last buffer is rejected with an error wrapping ErrInvalidPacket, mirroring socket.io-parser's binary reconstructor, which throws "illegal attachments" rather than handing the raw placeholder object to the application. The index and the buffer count are both attacker-controlled (they arrive on the wire), so a mismatch is a malformed frame, not data.
func ReservedEvents ¶ added in v0.3.0
func ReservedEvents() []string
ReservedEvents returns a sorted copy of the reserved event names, useful for documentation and tooling.
func ValidateEventName ¶ added in v0.3.0
ValidateEventName checks that name is usable as an application event name: it must be non-empty and must not collide with a reserved Socket.IO event. It returns ErrEmptyEvent or ErrReservedEvent on failure, and nil when the name is safe to emit.
Types ¶
type Adapter ¶
type Adapter interface {
// Add registers a socket in the namespace.
Add(socketID string, s *Socket)
// Remove deletes a socket and drops it from every room.
Remove(socketID string)
// Join adds a socket to a room.
Join(socketID, room string)
// Leave removes a socket from a room.
Leave(socketID, room string)
// SocketsInRoom returns the sockets that are members of a room.
SocketsInRoom(room string) []*Socket
// AllSockets returns every socket in the namespace.
AllSockets() []*Socket
// Get returns a socket by id.
Get(socketID string) (*Socket, bool)
}
Adapter stores which sockets belong to a namespace and its rooms. The default implementation keeps everything in process; supplying a custom Adapter (e.g. backed by Redis) is the extension point for scaling a namespace across multiple server instances.
type BroadcastOperator ¶
type BroadcastOperator struct {
// contains filtered or unexported fields
}
BroadcastOperator emits events to a filtered set of sockets in a namespace — optionally scoped to one or more rooms and excluding specific sockets. It is returned by Namespace.To, Server.To, and Socket.To, and is the equivalent of io.to(room).emit(...).
func (*BroadcastOperator) Compress ¶
func (b *BroadcastOperator) Compress(on bool) *BroadcastOperator
Compress sets whether the payload should be compressed by the transport. It is advisory in this implementation.
func (*BroadcastOperator) Emit ¶
func (b *BroadcastOperator) Emit(event string, args ...any)
Emit sends an event to every socket matched by the operator. When a cluster Broadcaster is installed, the broadcast is published to all nodes (which each deliver it to their local sockets); otherwise it is delivered locally.
func (*BroadcastOperator) Except ¶
func (b *BroadcastOperator) Except(socketID string) *BroadcastOperator
Except excludes a socket id from the broadcast.
func (*BroadcastOperator) ExceptRoom ¶ added in v0.3.0
func (b *BroadcastOperator) ExceptRoom(rooms ...string) *BroadcastOperator
ExceptRoom excludes every socket that is a member of any of the given rooms from the broadcast — the equivalent of io.except(room). Unlike Except, which excludes an individual socket id, ExceptRoom excludes whole rooms.
func (*BroadcastOperator) In ¶
func (b *BroadcastOperator) In(room string) *BroadcastOperator
In is an alias for To, matching socket.io's io.in(room).
func (*BroadcastOperator) Local ¶ added in v0.3.0
func (b *BroadcastOperator) Local() *BroadcastOperator
Local restricts the broadcast to sockets connected to this node, suppressing cluster fan-out even when a Broadcaster is installed — the equivalent of io.local.emit(...). It is a no-op on a single-node deployment.
func (*BroadcastOperator) Timeout ¶ added in v0.3.0
func (b *BroadcastOperator) Timeout(d time.Duration) *BroadcastOperator
Timeout records an acknowledgement timeout for the broadcast, matching io.timeout(ms).emit(...). It is stored for callers that aggregate acks; the convenience Emit itself is fire-and-forget.
func (*BroadcastOperator) To ¶
func (b *BroadcastOperator) To(room string) *BroadcastOperator
To narrows the broadcast to an additional room.
func (*BroadcastOperator) Volatile ¶
func (b *BroadcastOperator) Volatile() *BroadcastOperator
Volatile marks the broadcast as volatile: messages that cannot be delivered immediately (e.g. to a client mid-reconnect) may be dropped. On this single-node, buffered implementation it is advisory.
type BroadcastTarget ¶
type BroadcastTarget interface {
Emitter
RoomTargeter
}
BroadcastTarget is the combination satisfied by the room-addressable emitters (server, namespace, and broadcast operator): they can both narrow to a room and emit to the resulting set.
type Broadcaster ¶
type Broadcaster interface {
// Publish sends a serialized broadcast to all instances (including this
// one — pub/sub echoes to the publisher).
Publish(data []byte) error
// OnMessage registers the handler invoked for every received broadcast.
OnMessage(func(data []byte))
// Close shuts the broadcaster down.
Close() error
}
Broadcaster fans broadcasts out to other server instances. Installing one (Server.SetBroadcaster) turns a single-node server into a cluster member: a broadcast is published once and each node delivers it to its own local sockets. The reference implementation is the Redis adapter in the redis subpackage, but any pub/sub transport can implement this interface.
type Decoder ¶ added in v0.3.0
type Decoder struct {
// contains filtered or unexported fields
}
Decoder reassembles a packet from the stream of transport frames produced by an Encoder. It is the Go equivalent of socket.io-parser's Decoder and is stateful: feed it frames with Add in arrival order and it returns a completed packet once all of a packet's frames have been seen. A single Decoder handles one packet at a time and must not be used concurrently.
func NewDecoder ¶ added in v0.3.0
func NewDecoder() *Decoder
NewDecoder returns an empty Decoder ready to accept frames.
func (*Decoder) Add ¶ added in v0.3.0
Add feeds one transport frame to the decoder. The frame must be a string (a text frame, i.e. a Socket.IO text packet) or a []byte (a binary attachment frame). Add returns a non-nil packet when the frame completes a packet, or (nil, nil) when more frames are required (a binary header awaiting its attachments). A malformed frame, or a binary attachment arriving with no header pending, returns ErrInvalidPacket.
Reassembled binary packets are rewritten to their plain EVENT/ACK type (with the attachment count cleared) once every {"_placeholder":true,"num":N} marker has been replaced by its buffer — mirroring socket.io-parser, whose Decoder hands consumers an ordinary EVENT/ACK. A placeholder index that points past the attachments supplied is rejected with an error wrapping ErrInvalidPacket.
type Emitter ¶
Emitter is anything that can broadcast an event to a set of sockets without a per-socket error — the server, a namespace, and a broadcast operator all fan an event out to many recipients, so a delivery error to any single socket is not surfaced. (Socket.Emit, which targets one client, deliberately returns an error and is therefore not an Emitter.)
type Encoder ¶ added in v0.3.0
type Encoder struct{}
Encoder encodes Socket.IO packets into the sequence of transport frames they occupy. It is the Go equivalent of socket.io-parser's Encoder: a plain, stateless value that is safe to share across goroutines.
func NewEncoder ¶ added in v0.3.0
func NewEncoder() *Encoder
NewEncoder returns a ready-to-use Encoder.
func (*Encoder) Encode ¶ added in v0.3.0
Encode renders a packet to the ordered frames it occupies on the wire. The first element is always the packet's text form (a Go string); when the packet carries []byte values the type is promoted to its binary variant, the text header declares the attachment count, and each following element is a raw binary buffer ([]byte). A caller writes element 0 as a text frame and every subsequent element as a binary frame, in order.
type EventHandler ¶
EventHandler handles an inbound event. It receives the event arguments and may return a non-nil slice to acknowledge the event (sent back to the client when the event requested an ack).
type Handshake ¶ added in v0.3.0
type Handshake struct {
// Headers is the set of HTTP request headers.
Headers http.Header
// Time is when the handshake was processed.
Time time.Time
// Address is the remote network address the connection originated from.
// It is the port-stripped RemoteAddr unless the handshake was built with
// NewProxiedHandshake or Options.TrustProxy, in which case a present
// X-Forwarded-For takes precedence. See NewHandshake for why the header is
// not trusted by default.
Address string
// XDomain reports whether the request carried an Origin header (a
// cross-origin, browser-initiated request).
XDomain bool
// Secure reports whether the request arrived over TLS (https/wss).
Secure bool
// Issued is the handshake time in Unix milliseconds, matching the JS
// handshake.issued field.
Issued int64
// URL is the request URI (path and query) of the handshake request.
URL string
// Query holds the parsed query-string parameters.
Query url.Values
// Auth is the authentication payload the client supplied with CONNECT.
Auth any
}
Handshake describes the details of the HTTP request that established a connection — the Go equivalent of Socket.IO's socket.handshake object. It is captured once, at connection time, and never changes for the life of the socket. Middleware and connection handlers inspect it to authorize or annotate a connection (origin, forwarded address, query string, auth payload).
func NewHandshake ¶ added in v0.3.0
NewHandshake builds a Handshake from an incoming HTTP request and the auth payload sent with the Socket.IO CONNECT packet. Address is taken from the request's RemoteAddr with any port stripped, matching engine.io, which reports the address of the socket the request actually arrived on.
X-Forwarded-For is deliberately NOT consulted: it is a client-supplied header that any peer can set to an arbitrary value, so honoring it unconditionally lets a client choose the address that authorization, rate limiting, and audit logging see. Use NewProxiedHandshake (or Options.TrustProxy on the server) only when the server sits behind a reverse proxy that overwrites the header.
func NewProxiedHandshake ¶ added in v0.5.0
NewProxiedHandshake is NewHandshake for a server running behind a trusted reverse proxy: Address is the first entry of the X-Forwarded-For header when one is present, falling back to RemoteAddr otherwise. Only use it when a proxy you control is guaranteed to replace (not append to) any client-supplied X-Forwarded-For, because the value is otherwise attacker-chosen.
type Namespace ¶
type Namespace struct {
// contains filtered or unexported fields
}
Namespace is a communication channel that partitions a Socket.IO server. Each namespace has its own set of connected sockets, rooms, and connection handlers. The default namespace is "/".
func (*Namespace) DisconnectSockets ¶
DisconnectSockets disconnects every socket in the namespace.
func (*Namespace) Except ¶ added in v0.3.0
func (ns *Namespace) Except(room string) *BroadcastOperator
Except returns a broadcast operator over the namespace that excludes every socket in the given room — the equivalent of io.except(room).
func (*Namespace) FetchSockets ¶
FetchSockets returns all sockets in the namespace (alias for Sockets, matching io.fetchSockets()).
func (*Namespace) In ¶ added in v0.3.0
func (ns *Namespace) In(room string) *BroadcastOperator
In is an alias for To, scoping a broadcast to a room within this namespace — the equivalent of io.in(room).
func (*Namespace) Local ¶ added in v0.3.0
func (ns *Namespace) Local() *BroadcastOperator
Local returns a broadcast operator over the namespace restricted to this node — the equivalent of io.local.
func (*Namespace) OnConnection ¶
OnConnection registers a handler invoked for each new socket that connects to this namespace.
func (*Namespace) SetAdapter ¶
SetAdapter replaces the namespace's room adapter (e.g. with a Redis-backed one for multi-node scale-out). Call it before any sockets connect.
func (*Namespace) SocketsInRoom ¶
SocketsInRoom returns the sockets that are members of a room.
func (*Namespace) SocketsJoin ¶
SocketsJoin makes every socket in the namespace join the given rooms.
func (*Namespace) SocketsLeave ¶
SocketsLeave makes every socket in the namespace leave the given rooms.
func (*Namespace) To ¶
func (ns *Namespace) To(room string) *BroadcastOperator
To returns a broadcast operator scoped to a room within this namespace.
func (*Namespace) Use ¶
Use registers connection middleware for the namespace. Each middleware runs for every incoming connection before the connection handler; calling next with a non-nil error rejects the connection with a CONNECT_ERROR carrying the error's message — the equivalent of io.use((socket, next) => ...).
type Options ¶
type Options struct {
// Path is the HTTP path the server handles (default "/socket.io/").
Path string
// PingInterval is how often the server sends heartbeat pings.
PingInterval time.Duration
// PingTimeout is how long the server waits for a pong before disconnecting.
PingTimeout time.Duration
// MaxPayload advertises the maximum HTTP payload size to clients.
MaxPayload int
// CheckOrigin, if set, authorizes cross-origin requests. When nil, all
// origins are allowed — including cross-site WebSocket handshakes, which
// are not subject to the same-origin policy. Set it on any deployment where
// a connection carries ambient authority (cookies, session headers).
CheckOrigin func(r *http.Request) bool
// TrustProxy makes Socket.Handshake().Address honor the first entry of the
// X-Forwarded-For header. Leave it false unless a reverse proxy you control
// overwrites that header: it is otherwise fully client-controlled.
TrustProxy bool
// MaxSessions caps the number of concurrent Engine.IO sessions the server
// will hold. Every unauthenticated handshake request allocates a session and
// a heartbeat goroutine that survive until the ping timeout elapses, so
// without a cap a request flood grows the session map without bound. When
// the cap is reached, further handshakes are refused with 503 while existing
// sessions keep working. Zero selects DefaultMaxSessions; a negative value
// disables the cap entirely.
MaxSessions int
}
Options configures a Server.
Example (TrustProxy) ¶
ExampleOptions_trustProxy shows when the X-Forwarded-For header may be believed. Socket.Handshake().Address defaults to the address of the socket the request actually arrived on, because X-Forwarded-For is a plain request header any client can set — trusting it unconditionally would let a client pick the address that rate limiting, authorization and audit logs see. Turn TrustProxy on only when a reverse proxy you control *overwrites* the header.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
socketio "github.com/malcolmston/socketio"
)
func main() {
behindProxy := socketio.New(socketio.Options{TrustProxy: true})
defer behindProxy.Close()
direct := socketio.New() // TrustProxy is false by default
defer direct.Close()
r := httptest.NewRequest(http.MethodGet, "/socket.io/", nil)
r.RemoteAddr = "10.0.0.9:51234"
r.Header.Set("X-Forwarded-For", "203.0.113.7, 10.0.0.1")
fmt.Println("trusted: ", socketio.NewProxiedHandshake(r, nil).Address)
fmt.Println("untrusted:", socketio.NewHandshake(r, nil).Address)
}
Output: trusted: 203.0.113.7 untrusted: 10.0.0.9
type Packet ¶
type Packet struct {
// Type is the packet's Socket.IO type (Connect, Event, Ack, ...).
Type PacketType
// Namespace the packet targets; defaults to "/".
Namespace string
// ID is the acknowledgement id when the packet requests or answers an ack;
// nil otherwise.
ID *uint64
// Data is the decoded JSON payload: an array for Event/Ack ([name, args...]
// or [args...]) and an object for Connect/ConnectError.
Data any
// contains filtered or unexported fields
}
Packet is a decoded Socket.IO protocol packet.
func DecodePacket ¶
DecodePacket parses a Socket.IO packet from its wire form.
func (Packet) Args ¶
Args returns the event arguments (everything after the event name) for an Event packet, or the full array for an Ack packet.
func (Packet) Attachments ¶
Attachments returns the declared number of binary attachments for a BINARY_EVENT/BINARY_ACK packet.
func (Packet) Encode ¶
Encode renders a packet to its Socket.IO wire form (the string carried inside an Engine.IO MESSAGE packet).
func (Packet) EncodeBinary ¶
EncodeBinary encodes a packet, extracting any binary attachments. When the payload contains []byte values the packet type is promoted to its binary variant and the buffers are returned separately; otherwise buffers is nil. It is exported for client implementations.
func (Packet) HasBinaryData ¶ added in v0.3.0
HasBinaryData reports whether the packet's payload contains any []byte values, i.e. whether encoding it will produce binary attachment frames.
type PacketType ¶
type PacketType byte
PacketType identifies a Socket.IO packet.
const ( // Connect initiates a namespace connection. Connect PacketType = iota // Disconnect leaves a namespace. Disconnect // Event carries an application event and its arguments. Event // Ack answers an Event that requested acknowledgement. Ack // ConnectError reports a failed namespace connection. ConnectError // BinaryEvent is an Event with binary attachments (decoded as text here). BinaryEvent // BinaryAck is an Ack with binary attachments (decoded as text here). BinaryAck )
func (PacketType) String ¶
func (t PacketType) String() string
String returns the Socket.IO name of the packet type (e.g. "EVENT", "ACK"), or "UNKNOWN" for an unrecognized value.
type RoomTargeter ¶
type RoomTargeter interface {
To(room string) *BroadcastOperator
}
RoomTargeter is anything that can scope a broadcast to a room, returning a *BroadcastOperator for further chaining (.To/.Except/.Emit). The server, a namespace, an individual socket, and an existing operator all expose this, mirroring socket.io's io.to(room) / socket.to(room) API.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a Socket.IO server. It implements http.Handler and should be mounted at its configured Path.
Example ¶
ExampleServer wires up a complete Socket.IO server the way a chat backend would. It creates a server with New, registers a connection handler that runs for every client that connects to the default namespace, and inside that handler joins the socket to a room, registers a "chat" event handler that re-broadcasts each message to everyone in that room via To(...).Emit, and installs a disconnect callback. The server is an http.Handler, so it is mounted here on an httptest.Server (a real net/http server would use http.Handle plus http.ListenAndServe at the same DefaultPath). The example then shuts everything down cleanly with Close on both the HTTP server and the Socket.IO server. The reader should take away the end-to-end shape of a server — New, OnConnection, Join, On, room broadcast, and mounting on HTTP — which mirrors the JavaScript io.on("connection")/socket.join/io.to().emit API.
package main
import (
"fmt"
"net/http/httptest"
socketio "github.com/malcolmston/socketio"
)
func main() {
io := socketio.New()
io.OnConnection(func(s *socketio.Socket) {
// Every socket joins a shared room on connect.
s.Join("room1")
// Re-broadcast each chat message to the whole room.
s.On("chat", func(args []any) []any {
io.To("room1").Emit("chat", args...)
return nil
})
s.OnDisconnect(func(reason string) {
// Clean up per-socket state here.
_ = reason
})
})
// A *Server is an http.Handler. In production:
//
// http.Handle(socketio.DefaultPath, io)
// http.ListenAndServe(":3000", nil)
//
// Here httptest hosts it so the example is self-contained.
ts := httptest.NewServer(io)
defer ts.Close()
defer io.Close()
fmt.Println("serving Socket.IO at", socketio.DefaultPath)
}
Output: serving Socket.IO at /socket.io/
func (*Server) Close ¶
func (s *Server) Close()
Close disconnects every connected session and shuts the server down. It does not stop the underlying http.Server (the caller owns that).
func (*Server) DisconnectSockets ¶
DisconnectSockets disconnects every socket in the default namespace.
func (*Server) Except ¶ added in v0.3.0
func (s *Server) Except(room string) *BroadcastOperator
Except returns a broadcast operator over the default namespace excluding every socket in the given room — the equivalent of io.except(room).
func (*Server) FetchSockets ¶
FetchSockets returns all sockets in the default namespace.
func (*Server) Handler ¶
Handler wraps the server so it intercepts Socket.IO requests (those under its Path) and delegates everything else to next — the Go equivalent of attaching Socket.IO to an existing HTTP server that is otherwise served by Express:
app := express.New() // your routes
io := socketio.New() // your socket handlers
http.ListenAndServe(":3000", io.Handler(app))
next may be any http.Handler (an *express.Application, an http.ServeMux, ...); pass nil to 404 non-Socket.IO requests.
func (*Server) In ¶ added in v0.3.0
func (s *Server) In(room string) *BroadcastOperator
In scopes a server-wide broadcast to a room in the default namespace — the equivalent of io.in(room).
func (*Server) Local ¶ added in v0.3.0
func (s *Server) Local() *BroadcastOperator
Local returns a broadcast operator over the default namespace restricted to this node — the equivalent of io.local.
func (*Server) Of ¶
Of returns the namespace with the given name, creating it if necessary. A name without a leading slash has one added.
func (*Server) OnConnection ¶
OnConnection registers a handler invoked when a socket connects to the default namespace. It is the equivalent of io.on("connection", ...).
func (*Server) OnServerEvent ¶
OnServerEvent registers a handler for server-side events delivered via ServerSideEmit. On this single-node implementation these are local; a multi-node deployment would relay them between servers through an adapter.
func (*Server) ServeHTTP ¶
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler and dispatches Engine.IO transport requests.
func (*Server) ServerSideEmit ¶
ServerSideEmit emits an event to other servers (and this one), the equivalent of io.serverSideEmit. Single-node: it invokes locally registered handlers.
func (*Server) SetBroadcaster ¶
func (s *Server) SetBroadcaster(b Broadcaster) *Server
SetBroadcaster installs a cluster broadcaster. Once set, every room/namespace broadcast is published through it and delivered to local sockets when the message is received back, so all nodes in the cluster stay in sync.
func (*Server) SocketsJoin ¶
SocketsJoin makes every socket in the default namespace join the given rooms.
func (*Server) SocketsLeave ¶
SocketsLeave makes every socket in the default namespace leave the given rooms.
func (*Server) To ¶
func (s *Server) To(room string) *BroadcastOperator
To returns a broadcast operator scoped to a room in the default namespace.
type Socket ¶
type Socket struct {
// contains filtered or unexported fields
}
Socket is a single client connection to a namespace. It is the primary object applications interact with — registering event handlers, emitting events, and joining rooms.
func (*Socket) Broadcast ¶
func (s *Socket) Broadcast() *BroadcastOperator
Broadcast returns an operator targeting every other socket in the namespace.
func (*Socket) Disconnect ¶
Disconnect closes the socket, optionally closing the underlying transport.
func (*Socket) Emit ¶
Emit sends an event to this socket. Emitting a reserved Socket.IO event name (connect, connect_error, disconnect, disconnecting, ...) returns ErrReservedEvent without sending anything, matching the JavaScript server, which throws for those names — they are the library's own lifecycle signals and must not be spoofed as application events.
func (*Socket) EmitAck ¶
EmitAck sends an event and blocks until the client acknowledges it or timeout elapses, returning the acknowledgement arguments. On timeout it returns ErrAckTimeout and forgets the acknowledgement, so a client that never answers (or answers late) cannot make the socket accumulate pending callbacks.
func (*Socket) EmitWithAck ¶
EmitWithAck sends an event and invokes ackFn with the client's acknowledgement arguments. If the packet cannot be sent the pending callback is discarded and the send error is returned; ackFn is not invoked.
func (*Socket) Except ¶ added in v0.3.0
func (s *Socket) Except(room string) *BroadcastOperator
Except returns a broadcast operator over the namespace that excludes this socket and every member of the given room — the equivalent of socket.except(room).
func (*Socket) GetString ¶
GetString returns a stored string value, or "" if missing / not a string.
func (*Socket) Handshake ¶ added in v0.5.0
Handshake returns the details of the HTTP request that established this socket's session — headers, query string, address, TLS flag, and the auth payload sent with CONNECT — the equivalent of socket.handshake in the JavaScript API. It is nil only for sockets created outside an HTTP request (in tests). The returned value is a per-socket snapshot; mutating it does not affect the session or other namespaces on it.
func (*Socket) In ¶ added in v0.3.0
func (s *Socket) In(room string) *BroadcastOperator
In returns a broadcast operator that targets a room while excluding this socket — the equivalent of socket.in(room). It behaves like To.
func (*Socket) ListenersAny ¶ added in v0.3.0
ListenersAny returns a snapshot of the currently registered catch-all listeners — the equivalent of socket.listenersAny().
func (*Socket) OffAny ¶ added in v0.3.0
OffAny removes catch-all listeners. Called with no argument it removes every catch-all listener; called with one listener it is a no-op placeholder for API symmetry (Go funcs are not comparable, so individual removal is not supported) and clears all listeners. Use ListenersAny to inspect the current set. Returns the socket for chaining.
func (*Socket) On ¶
func (s *Socket) On(event string, handler EventHandler) *Socket
On registers a handler for an event.
func (*Socket) OnAny ¶ added in v0.3.0
OnAny registers a catch-all listener invoked for every inbound event on this socket, in registration order and before the named handlers run. The callback receives the event name and its arguments. Returns the socket for chaining — the equivalent of socket.onAny(listener).
func (*Socket) OnDisconnect ¶
OnDisconnect registers a callback invoked when the socket disconnects.
func (*Socket) OnDisconnecting ¶ added in v0.5.0
OnDisconnecting registers a callback invoked as the socket begins to disconnect, before it has left its rooms — the equivalent of socket.on("disconnecting"). Unlike OnDisconnect, the socket's Rooms() are still populated when this fires, so a handler can record which rooms the socket was in on its way out. It runs with the same reason string that the subsequent disconnect carries.
func (*Socket) PendingAcks ¶ added in v0.5.0
PendingAcks reports how many acknowledgements this socket is still waiting for. It is primarily useful in tests and for instrumentation.
func (*Socket) PrependAny ¶ added in v0.3.0
PrependAny registers a catch-all listener at the front of the catch-all chain, so it runs before previously registered catch-all listeners — the equivalent of socket.prependAny(listener).
func (*Socket) Set ¶
Set stores an arbitrary value on the socket, persisting for the lifetime of the connection — the equivalent of Socket.IO's socket.data. Use it to attach session-like state (the authenticated user, a tenant id, ...).
func (*Socket) To ¶
func (s *Socket) To(room string) *BroadcastOperator
To returns a broadcast operator that targets a room, excluding this socket — the equivalent of socket.to(room).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package client is a Go Socket.IO client.
|
Package client is a Go Socket.IO client. |
|
docs
|
|
|
gen
command
Command gendocs generates a static HTML documentation site for a Go module using only the standard library (go/doc, go/parser).
|
Command gendocs generates a static HTML documentation site for a Go module using only the standard library (go/doc, go/parser). |
|
Package engineio implements the Engine.IO v4 protocol codec — the transport framing layer that Socket.IO is built on.
|
Package engineio implements the Engine.IO v4 protocol codec — the transport framing layer that Socket.IO is built on. |
|
examples
|
|
|
chat
command
Command chat is a small Socket.IO server in Go: it echoes messages and broadcasts chat messages to everyone in a room.
|
Command chat is a small Socket.IO server in Go: it echoes messages and broadcasts chat messages to everyone in a room. |
|
client
command
Command client connects to a Socket.IO server using the Go client and exchanges a couple of events.
|
Command client connects to a Socket.IO server using the Go client and exchanges a couple of events. |
|
internal
|
|
|
ws
Package ws is a minimal, dependency-free RFC 6455 WebSocket implementation, sufficient to carry Engine.IO/Socket.IO traffic.
|
Package ws is a minimal, dependency-free RFC 6455 WebSocket implementation, sufficient to carry Engine.IO/Socket.IO traffic. |
|
Package redis provides a Redis-backed Broadcaster for socketio, enabling multi-node scale-out: broadcasts are relayed between server instances over Redis pub/sub so a message emitted on one node reaches sockets connected to any node.
|
Package redis provides a Redis-backed Broadcaster for socketio, enabling multi-node scale-out: broadcasts are relayed between server instances over Redis pub/sub so a message emitted on one node reaches sockets connected to any node. |