Documentation
¶
Overview ¶
Package client is a Go Socket.IO client. It connects to a Socket.IO server (this module's server or the Node reference server) over the WebSocket transport and provides the familiar On/Emit/EmitWithAck API that mirrors the browser socket.io-client, letting a Go process act as a first-class Socket.IO peer rather than merely a server:
c, err := client.Dial("http://localhost:3000")
c.On("news", func(args []any) []any { fmt.Println(args); return nil })
c.Emit("hello", "world")
Use it whenever a Go program needs to talk to a Socket.IO endpoint as a client: integration and end-to-end tests against a running server, service-to- service messaging where the other side already speaks Socket.IO, bots and load generators, or bridging a Socket.IO event stream into another system. Dial blocks until the Socket.IO CONNECT handshake for the chosen namespace completes (or DialTimeout elapses), so a successful return means the client is fully connected and ready to Emit. Optional authentication data is sent with the CONNECT packet via Options.Auth, matching the server-side socket.Auth().
Addressing follows socket.io-client: the PATH of the dialled URL names the namespace, so Dial("http://host/admin") connects to namespace "/admin", while the HTTP path the server is mounted on is Options.Path (default DefaultPath, "/socket.io/"). See ResolveEndpoint for the full set of rules.
Under the hood the client speaks Engine.IO v4 directly over a single WebSocket (it does not use HTTP long-polling): it dials ws(s)://.../socket.io/ with EIO=4&transport=websocket, reads the Engine.IO OPEN handshake, sends the Socket.IO CONNECT packet, and then runs a background read loop that answers heartbeat pings, dispatches inbound EVENT packets to handlers registered with On, and resolves acknowledgements. Outgoing events are encoded with the shared socketio packet codec, so binary ([]byte) arguments are automatically split into BINARY_EVENT attachment frames and reassembled on receipt.
Concurrency and lifecycle: On, Emit, EmitWithAck, and Close are safe to call from multiple goroutines. Handlers run on the read-loop goroutine, so a handler that itself blocks on EmitWithAck should hand off to another goroutine to avoid deadlocking the loop; returning a non-nil slice from a handler acknowledges an event the server sent with an ack id. EmitWithAck waits up to the supplied timeout and returns an error if no acknowledgement arrives. When Options.Reconnection is enabled, an unexpected transport drop triggers a reconnect loop on the same schedule as the JavaScript client's Manager — a Backoff seeded with ReconnectionDelay, ReconnectionDelayMax and RandomizationFactor, bounded by ReconnectionAttempts — and fires the local "reconnect" event; a user-initiated Close suppresses reconnection. Note that socket.io-client reconnects by default whereas Options.Reconnection must be set explicitly, because a Go zero value cannot express a true default.
Compared with the JavaScript socket.io-client the surface is intentionally small: one namespace per Client, no long-polling fallback, and lifecycle events surfaced as ordinary events ("disconnect", "reconnect") registered through On. The wire protocol is identical, so this client interoperates with the Node reference server and with this module's server package. For multi-namespace usage, Dial a separate Client per namespace.
Index ¶
Examples ¶
Constants ¶
const DefaultPath = "/socket.io/"
DefaultPath is the HTTP path a Socket.IO server is mounted on, and the path the client requests unless Options.Path says otherwise. It is the counterpart of socket.io-client's `path` option (whose default is "/socket.io").
Variables ¶
This section is empty.
Functions ¶
func ConnectPacket ¶ added in v0.5.0
ConnectPacket builds the Socket.IO CONNECT packet that opens a namespace, carrying auth as its payload when non-nil. It is what Dial sends after the Engine.IO handshake, and the counterpart of socket.io-client's Socket._sendConnectPacket.
func EventPacket ¶ added in v0.5.0
EventPacket builds the EVENT packet an Emit sends: the event name followed by its arguments, addressed to namespace. A non-nil ackID asks the server for an acknowledgement under that id (what EmitWithAck does). Reserved Socket.IO event names are refused with socketio.ErrReservedEvent, matching socket.io-client, which throws for them.
Types ¶
type AckIDs ¶ added in v0.5.0
type AckIDs struct {
// contains filtered or unexported fields
}
AckIDs allocates the acknowledgement ids an emit claims: 0 for the first acknowledged emit on a connection, then 1, 2, … . It is the counterpart of socket.io-client's Socket.ids counter, and every Client owns one. The zero value is ready to use and is safe for concurrent use.
type Backoff ¶ added in v0.3.0
type Backoff struct {
// contains filtered or unexported fields
}
Backoff computes an exponentially increasing delay with optional jitter, mirroring the JavaScript client's backo2 module used for reconnection. Each call to Duration returns the delay for the current attempt and advances the attempt counter; Reset returns to the initial delay. A Backoff is safe for concurrent use.
func NewBackoff ¶ added in v0.3.0
func NewBackoff(opts BackoffOptions) *Backoff
NewBackoff creates a Backoff from the supplied options, filling in defaults for any zero field.
func (*Backoff) Attempts ¶ added in v0.3.0
Attempts returns how many times Duration has been called since the last Reset.
func (*Backoff) Duration ¶ added in v0.3.0
Duration returns the delay for the current attempt and advances to the next, computing exactly what backo2 (the module socket.io-client's Manager uses) would compute:
ms = min * factor^attempt // NOT clamped yet deviation = floor(rand * jitter * ms) // jitter only, when a Rand is set ms = ms ± deviation // sign from the same draw result = trunc(min(ms, max)) in whole milliseconds
The order matters: the ceiling is applied after the jitter, not before, and the deviation is floored to whole milliseconds — so a jittered delay derived from an already-clamped base (or from an unfloored deviation) would drift away from the JavaScript client's schedule.
type BackoffOptions ¶ added in v0.3.0
type BackoffOptions struct {
// Min is the initial delay (default 100ms).
Min time.Duration
// Max is the ceiling the delay is clamped to (default 10s).
Max time.Duration
// Factor is the exponential growth base per attempt (default 2).
Factor float64
// Jitter is the randomization fraction in (0,1]; 0 — or any value outside
// that range, as in backo2 — disables jitter. A value of 0.5 spreads each
// delay by up to ±50%. Jitter is only applied when Rand is non-nil, keeping
// a default Backoff fully deterministic.
Jitter float64
// Rand supplies the randomness used for jitter; it must return a value in
// [0,1). Inject a deterministic function in tests, or math/rand's Float64 in
// production. When nil, no jitter is applied.
Rand func() float64
}
BackoffOptions configures a Backoff. The zero value yields sensible defaults (100ms initial, 10s ceiling, factor 2, no jitter).
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a connected Socket.IO client.
func Dial ¶
Dial connects to a Socket.IO server at rawURL (http:// or ws://) and returns once the Socket.IO CONNECT handshake completes. The path of rawURL names the namespace, as in socket.io-client — see ResolveEndpoint.
Example ¶
ExampleDial demonstrates the full lifecycle of a Go Socket.IO client. It calls Dial to connect to a running server, passing Options to select a namespace and to enable automatic reconnection; Dial blocks until the CONNECT handshake completes, so a nil error means the client is ready to use. The example then registers a handler for the server-pushed "news" event with On, sends a fire-and-forget event with Emit, and issues a request/response round-trip with EmitWithAck that waits up to five seconds for the server's acknowledgement. Finally it closes the connection with a deferred Close, which also suppresses any pending reconnection. The reader should take away how a Go program becomes a first-class Socket.IO peer using the same On/Emit/EmitWithAck vocabulary as the browser client. (This example is compiled to verify the API but is not run here, since it needs a live server to connect to.)
package main
import (
"fmt"
"log"
"time"
"github.com/malcolmston/socketio/client"
)
func main() {
c, err := client.Dial("http://localhost:3000", client.Options{
Namespace: "/",
Auth: map[string]any{"token": "s3cret"},
Reconnection: true,
})
if err != nil {
log.Fatal(err)
}
defer c.Close()
// Handle events the server pushes to us.
c.On("news", func(args []any) []any {
fmt.Println("news:", args)
return nil
})
// Fire-and-forget emit.
if err := c.Emit("hello", "world"); err != nil {
log.Fatal(err)
}
// Request/response with an acknowledgement.
reply, err := c.EmitWithAck("question", 5*time.Second, "what is 2+2?")
if err != nil {
log.Fatal(err)
}
fmt.Println("answer:", reply)
}
Output:
func (*Client) Emit ¶
Emit sends an event to the server. Emitting a reserved Socket.IO event name (connect, connect_error, disconnect, disconnecting, ...) returns socketio.ErrReservedEvent without sending anything, matching socket.io-client, which throws for those names.
func (*Client) EmitWithAck ¶
EmitWithAck sends an event and waits for the server's acknowledgement, up to timeout.
type Endpoint ¶ added in v0.5.0
type Endpoint struct {
// URL is the ws:// or wss:// URL of the Engine.IO endpoint, including the
// mandatory EIO=4&transport=websocket query.
URL string
// Namespace is the Socket.IO namespace the CONNECT packet will name.
Namespace string
}
Endpoint is where a Dial will actually connect: the Engine.IO WebSocket URL and the Socket.IO namespace to hand shake for. It is the result of applying socket.io-client's addressing rules to a user-supplied URL.
func ResolveEndpoint ¶ added in v0.5.0
ResolveEndpoint derives the Engine.IO WebSocket URL and the Socket.IO namespace from a user-supplied URL, following the same rules as socket.io-client's url() plus Transport.uri():
- http/ws become ws, https/wss become wss;
- the URL's PATH is the namespace, not the HTTP path — io("http://h/admin") connects to namespace "/admin". The HTTP path the server is mounted on is Options.Path (default DefaultPath), never taken from the URL;
- a port that is the default for the scheme (80 for ws, 443 for wss) is omitted, so ws://h:80/ and ws://h/ produce one endpoint, not two;
- userinfo is dropped: Engine.IO addresses the host, and credentials belong in Options.Auth or a header, not in the transport URL;
- any query in the URL is preserved, with EIO=4 and transport=websocket added.
An explicit Options.Namespace wins over the namespace in the URL. Dial calls this; it is exported so that addressing can be inspected (and tested) without opening a connection.
type Handler ¶
Handler handles an inbound event. Returning a non-nil slice acknowledges an event the server sent with an ack id.
type Options ¶
type Options struct {
// Namespace to connect to. When empty the namespace is taken from the path
// of the dialled URL (socket.io-client's rule), which is "/" for a bare
// host.
Namespace string
// Path is the HTTP path the server is mounted on (default DefaultPath). It
// is never taken from the dialled URL, whose path names the namespace. A
// missing trailing slash is added.
Path string
// Auth is an optional payload sent with the CONNECT packet.
Auth any
// DialTimeout bounds the connection handshake (default 20s, matching
// socket.io-client's `timeout`).
DialTimeout time.Duration
// Reconnection enables automatic reconnection after an unexpected
// disconnect. Note that socket.io-client reconnects by DEFAULT; a Go zero
// value cannot express a true default, so this must be set explicitly (see
// API-DEVIATIONS.md).
Reconnection bool
// ReconnectionAttempts bounds reconnection tries (0 = unlimited, which is
// socket.io-client's Infinity default).
ReconnectionAttempts int
// ReconnectionDelay is the initial delay between attempts (default 1s).
ReconnectionDelay time.Duration
// ReconnectionDelayMax is the ceiling the delay grows to (default 5s).
ReconnectionDelayMax time.Duration
// RandomizationFactor spreads each reconnection delay by up to this
// fraction, in [0,1]. socket.io-client defaults it to 0.5; a Go zero value
// cannot express that default, so jitter is off unless set (see
// API-DEVIATIONS.md).
RandomizationFactor float64
}
Options configures Dial. The zero value is usable: every field below states the default applied by WithDefaults.
func (Options) WithDefaults ¶ added in v0.5.0
WithDefaults returns a copy of o with every unset field filled in with the default documented on it. Dial and ResolveEndpoint both apply it, so the options a Client runs with are always the normalised ones.