engineio

package
v0.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 4 Imported by: 0

README

engineio — implements the Engine.IO v4 protocol codec

Go Reference Parity

Package engineio implements the Engine.IO v4 protocol codec — the transport framing layer that Socket.IO is built on. It encodes and decodes the small set of Engine.IO packets and the polling "payload" that batches several packets into a single HTTP body. Engine.IO is the lower half of the Node.js Socket.IO stack: it establishes and keeps alive a logical connection over an interchangeable transport (HTTP long-polling first, then an upgrade to WebSocket) and provides ordered, heartbeat-monitored delivery of opaque messages. This package is the Go equivalent of that layer's parser/encoder.

The wire format is deliberately tiny. A string packet is a single ASCII digit type prefix followed by the packet data — for example "4hello" is a MESSAGE ("4") carrying "hello", "2probe" is a PING with data "probe", and "0{...}" is the OPEN handshake carrying JSON. The seven packet types (Open, Close, Ping, Pong, Message, Upgrade, Noop) are modeled by PacketType and its constants. Encode and Decode convert between a Packet and this string form; that string is exactly what travels inside a WebSocket text frame.

Install

go get github.com/malcolmston/socketio@v0.5.0
import "github.com/malcolmston/socketio/engineio"

Usage

This is the package's own Example, so it compiles and its output is asserted on every go test ./engineio/.

fmt.Println(engineio.NewMessage("hello").Encode())

	p, _ := engineio.Decode("4hello")
	fmt.Printf("%s %q\n", p.Type, p.Data)

	payload := engineio.EncodePayload([]engineio.Packet{
		engineio.NewOpen(`{"sid":"abc"}`),
		engineio.NewMessage("hi"),
	})
	fmt.Printf("%q\n", payload)
4hello
message "hello"
"0{\"sid\":\"abc\"}\x1e4hi"

Exported surface

Functions
Function What it does
func EncodeCloseFrame(code CloseCode, reason string) []byte EncodeCloseFrame builds the payload of a WebSocket close frame: the two-byte big-endian status code followed by the UTF-8 reason.
func EncodePayload(packets []Packet) string EncodePayload joins packets into a single polling payload, separated by the record separator.
Types
Type What it is
CloseCode CloseCode is a WebSocket close status code (RFC 6455 §7.4).
Packet Packet is a single Engine.IO packet.
PacketType PacketType identifies an Engine.IO packet.
CloseCode — constructors and methods
Signature What it does
func DecodeCloseFrame(payload []byte) (CloseCode, string, error) DecodeCloseFrame parses a WebSocket close-frame payload into its status code and reason.
func (c CloseCode) IsValid() bool IsValid reports whether the code may legally be sent in a close frame.
func (c CloseCode) String() string String returns a short human-readable description of the close code, e.g.
Packet — constructors and methods
Signature What it does
func Decode(s string) (Packet, error) Decode parses a single packet from its string wire form.
func DecodePayload(payload string) ([]Packet, error) DecodePayload splits a polling payload into its constituent packets.
func NewBinaryMessage(data []byte) Packet NewBinaryMessage builds a MESSAGE packet carrying raw binary data.
func NewClose() Packet NewClose builds a CLOSE packet, which requests that the transport be closed.
func NewMessage(data string) Packet NewMessage builds a MESSAGE packet with string data.
func NewNoop() Packet NewNoop builds a NOOP packet, which does nothing and is used to cleanly terminate a pending long-polling request when the transport upgrades.
func NewOpen(handshakeJSON string) Packet NewOpen builds an OPEN packet carrying handshake JSON.
func NewPing(data string) Packet NewPing builds a PING packet.
func NewPong(data string) Packet NewPong builds a PONG packet answering a PING, echoing its data (an empty string for a heartbeat pong, "probe" for the upgrade handshake).
func NewUpgrade() Packet NewUpgrade builds an UPGRADE packet, sent by the client to complete a polling-to-WebSocket transport upgrade.
func (p Packet) Encode() string Encode renders a packet to its string wire form (used by the websocket transport for text frames).
func (p Packet) IsBinary() bool IsBinary reports whether the packet is a binary MESSAGE (its Binary field is set), as opposed to a text packet.
PacketType — constructors and methods
Signature What it does
func (t PacketType) String() string String returns the lowercase Engine.IO name of the packet type (e.g.
Constants

Protocol

Variables

ErrEmptyPacket, ErrInvalidCloseFrame

Full signatures, doc comments and every runnable example are on pkg.go.dev.

Measured parity

Compared case-for-case against engine.io-parser@5.2.3; engine.io-parser@5.2.3 + engine.io@6.6.4 by the harness in parity/socket.io/nested/engineio:

Parity 100%
Cases 101
Matching 96
Mismatching 0
Declared deviations 5

Regenerate from the aggregator repo with go test ./parity/socket.io/nested/engineio/. Declared deviations are documented differences, excluded from the denominator and listed in the harness report.

Deviations from upstream

Deliberate differences for this package, where any exist, are recorded in the module-wide API-DEVIATIONS.md.

License

MIT, as part of github.com/malcolmston/socketio. An independent re-implementation, not affiliated with or endorsed by the original project.

Documentation

Overview

Package engineio implements the Engine.IO v4 protocol codec — the transport framing layer that Socket.IO is built on. It encodes and decodes the small set of Engine.IO packets and the polling "payload" that batches several packets into a single HTTP body. Engine.IO is the lower half of the Node.js Socket.IO stack: it establishes and keeps alive a logical connection over an interchangeable transport (HTTP long-polling first, then an upgrade to WebSocket) and provides ordered, heartbeat-monitored delivery of opaque messages. This package is the Go equivalent of that layer's parser/encoder.

The wire format is deliberately tiny. A string packet is a single ASCII digit type prefix followed by the packet data — for example "4hello" is a MESSAGE ("4") carrying "hello", "2probe" is a PING with data "probe", and "0{...}" is the OPEN handshake carrying JSON. The seven packet types (Open, Close, Ping, Pong, Message, Upgrade, Noop) are modeled by PacketType and its constants. Encode and Decode convert between a Packet and this string form; that string is exactly what travels inside a WebSocket text frame.

Use this package when you need to speak the transport layer directly rather than the higher-level Socket.IO event API: the socketio server uses it to frame outbound messages and to interpret inbound transport packets, and the client package uses it to read the OPEN handshake and answer heartbeats. Most application code never imports engineio directly — it is a building block — but it is exported so alternative transports or tooling can reuse the codec.

For HTTP long-polling, several packets are batched into one response body using EncodePayload/DecodePayload, which join packets with the Engine.IO v4 record separator (ASCII 0x1e). Binary data is handled two ways: over WebSocket a binary MESSAGE rides in a native binary frame (its Binary field is set and Encode falls back to a base64 "b"-prefixed string only when forced into a text context), while in a polling payload a binary packet is always serialized as "b" + standard-base64. NewMessage and NewOpen are convenience constructors for the two packet types callers build most often.

The codec is pure and stateless: functions do not retain the byte slices they return, hold no locks, and perform no I/O, so they are safe to call concurrently. Decode and DecodePayload validate their input and return ErrEmptyPacket or a descriptive error for malformed data rather than panicking. This is a focused implementation of Engine.IO v4 (Protocol == 4) covering exactly what Socket.IO v5 needs; it is not a general transport manager and does not itself open sockets, schedule pings, or negotiate upgrades — those responsibilities live in the socketio and client packages.

Example

Example demonstrates the Engine.IO codec end to end. It first builds a MESSAGE packet with NewMessage and renders it to the wire form with Encode, showing the single-digit type prefix ("4") in front of the data. It then parses a wire string back into a Packet with Decode and prints the decoded type (via PacketType.String) and data. Finally it batches an OPEN handshake packet and a MESSAGE packet into a single HTTP long-polling body with EncodePayload, whose parts are joined by the Engine.IO v4 record separator (shown here as the \x1e escape). The reader should take away that Encode/Decode handle one packet and EncodePayload/DecodePayload handle the batched polling form, and that the wire format is a compact, human-readable string.

package main

import (
	"fmt"

	"github.com/malcolmston/socketio/engineio"
)

func main() {
	// Encode a single MESSAGE packet to its wire form.
	fmt.Println(engineio.NewMessage("hello").Encode())

	// Decode a wire string back into a Packet.
	p, _ := engineio.Decode("4hello")
	fmt.Printf("%s %q\n", p.Type, p.Data)

	// Batch several packets into one polling payload.
	payload := engineio.EncodePayload([]engineio.Packet{
		engineio.NewOpen(`{"sid":"abc"}`),
		engineio.NewMessage("hi"),
	})
	fmt.Printf("%q\n", payload)

}
Output:
4hello
message "hello"
"0{\"sid\":\"abc\"}\x1e4hi"

Index

Examples

Constants

View Source
const Protocol = 4

Protocol is the Engine.IO protocol revision implemented here.

Variables

View Source
var ErrEmptyPacket = errors.New("engineio: empty packet")

ErrEmptyPacket is returned when decoding an empty packet string.

View Source
var ErrInvalidCloseFrame = errors.New("engineio: invalid close frame payload")

ErrInvalidCloseFrame indicates a close-frame payload that is neither empty nor a valid two-byte-code (optionally UTF-8 reason) body.

Functions

func EncodeCloseFrame added in v0.3.0

func EncodeCloseFrame(code CloseCode, reason string) []byte

EncodeCloseFrame builds the payload of a WebSocket close frame: the two-byte big-endian status code followed by the UTF-8 reason. Pass an empty reason for a code-only frame.

func EncodePayload

func EncodePayload(packets []Packet) string

EncodePayload joins packets into a single polling payload, separated by the record separator.

Types

type CloseCode added in v0.3.0

type CloseCode uint16

CloseCode is a WebSocket close status code (RFC 6455 §7.4). Engine.IO runs its WebSocket transport over RFC 6455 frames, so a close frame's two-byte status code — and any UTF-8 reason that follows it — is the signal that carries why a transport shut down. These helpers encode and decode that payload without pulling in a third-party WebSocket library.

const (
	// CloseNormalClosure (1000) indicates a normal closure: the purpose for
	// which the connection was established has been fulfilled.
	CloseNormalClosure CloseCode = 1000
	// CloseGoingAway (1001) indicates an endpoint is going away, e.g. a server
	// shutting down or a browser navigating away from a page.
	CloseGoingAway CloseCode = 1001
	// CloseProtocolError (1002) indicates termination due to a protocol error.
	CloseProtocolError CloseCode = 1002
	// CloseUnsupportedData (1003) indicates receipt of a data type the endpoint
	// cannot accept (e.g. binary where only text is understood).
	CloseUnsupportedData CloseCode = 1003
	// CloseNoStatusReceived (1005) is a reserved pseudo-code meaning no status
	// code was present in the close frame. It must not be sent on the wire.
	CloseNoStatusReceived CloseCode = 1005
	// CloseAbnormalClosure (1006) is a reserved pseudo-code meaning the
	// connection closed without a close frame. It must not be sent on the wire.
	CloseAbnormalClosure CloseCode = 1006
	// CloseInvalidFramePayloadData (1007) indicates a message contained data
	// inconsistent with its type (e.g. non-UTF-8 in a text message).
	CloseInvalidFramePayloadData CloseCode = 1007
	// ClosePolicyViolation (1008) indicates a message violated the endpoint's
	// policy.
	ClosePolicyViolation CloseCode = 1008
	// CloseMessageTooBig (1009) indicates a message was too big to process —
	// the code Engine.IO uses when maxHttpBufferSize is exceeded.
	CloseMessageTooBig CloseCode = 1009
	// CloseMandatoryExtension (1010) indicates the client expected the server
	// to negotiate an extension that it did not.
	CloseMandatoryExtension CloseCode = 1010
	// CloseInternalServerErr (1011) indicates the server hit an unexpected
	// condition that prevented it from fulfilling the request.
	CloseInternalServerErr CloseCode = 1011
	// CloseServiceRestart (1012) indicates the server is restarting.
	CloseServiceRestart CloseCode = 1012
	// CloseTryAgainLater (1013) indicates the server is overloaded and the
	// client should retry after a delay.
	CloseTryAgainLater CloseCode = 1013
	// CloseTLSHandshake (1015) is a reserved pseudo-code meaning the TLS
	// handshake failed. It must not be sent on the wire.
	CloseTLSHandshake CloseCode = 1015
)

RFC 6455 §7.4.1 registered close codes, plus the pseudo-codes reserved for applications that never appear on the wire.

func DecodeCloseFrame added in v0.3.0

func DecodeCloseFrame(payload []byte) (CloseCode, string, error)

DecodeCloseFrame parses a WebSocket close-frame payload into its status code and reason. An empty payload decodes to CloseNoStatusReceived with an empty reason (per RFC 6455, a close frame may omit the status code). A one-byte payload is invalid and returns ErrInvalidCloseFrame.

func (CloseCode) IsValid added in v0.3.0

func (c CloseCode) IsValid() bool

IsValid reports whether the code may legally be sent in a close frame. The reserved pseudo-codes (1005, 1006, 1015) and codes below 1000 or in the unassigned 1016–2999 range are not valid to send; 3000–4999 (library and application use) are accepted.

func (CloseCode) String added in v0.3.0

func (c CloseCode) String() string

String returns a short human-readable description of the close code, e.g. "normal closure" for 1000, or "close code <n>" for an unregistered value.

type Packet

type Packet struct {
	// Type is the Engine.IO packet type (Open, Message, Ping, ...).
	Type PacketType
	// Data is the textual payload for string packets.
	Data string
	// Binary holds raw bytes for binary packets; when non-nil the packet is
	// encoded with a "b" prefix + base64 in polling payloads.
	Binary []byte
}

Packet is a single Engine.IO packet.

func Decode

func Decode(s string) (Packet, error)

Decode parses a single packet from its string wire form.

func DecodePayload

func DecodePayload(payload string) ([]Packet, error)

DecodePayload splits a polling payload into its constituent packets.

func NewBinaryMessage added in v0.3.0

func NewBinaryMessage(data []byte) Packet

NewBinaryMessage builds a MESSAGE packet carrying raw binary data. In a polling payload it is serialized as "b" + base64; over WebSocket it rides in a native binary frame.

func NewClose added in v0.3.0

func NewClose() Packet

NewClose builds a CLOSE packet, which requests that the transport be closed.

func NewMessage

func NewMessage(data string) Packet

NewMessage builds a MESSAGE packet with string data.

func NewNoop added in v0.3.0

func NewNoop() Packet

NewNoop builds a NOOP packet, which does nothing and is used to cleanly terminate a pending long-polling request when the transport upgrades.

func NewOpen

func NewOpen(handshakeJSON string) Packet

NewOpen builds an OPEN packet carrying handshake JSON.

func NewPing added in v0.3.0

func NewPing(data string) Packet

NewPing builds a PING packet. Engine.IO v4 servers send PING with an empty payload for the heartbeat; the "probe" data ("2probe") is used during a transport upgrade.

func NewPong added in v0.3.0

func NewPong(data string) Packet

NewPong builds a PONG packet answering a PING, echoing its data (an empty string for a heartbeat pong, "probe" for the upgrade handshake).

func NewUpgrade added in v0.3.0

func NewUpgrade() Packet

NewUpgrade builds an UPGRADE packet, sent by the client to complete a polling-to-WebSocket transport upgrade.

func (Packet) Encode

func (p Packet) Encode() string

Encode renders a packet to its string wire form (used by the websocket transport for text frames). Binary packets return their base64 form here.

func (Packet) IsBinary added in v0.3.0

func (p Packet) IsBinary() bool

IsBinary reports whether the packet is a binary MESSAGE (its Binary field is set), as opposed to a text packet.

type PacketType

type PacketType byte

PacketType identifies an Engine.IO packet.

const (
	// Open is sent by the server on connection with the handshake data.
	Open PacketType = iota
	// Close requests the transport be closed.
	Close
	// Ping is part of the heartbeat (server -> client in EIO4).
	Ping
	// Pong answers a Ping.
	Pong
	// Message carries an application payload (a Socket.IO packet).
	Message
	// Upgrade signals a transport upgrade (polling -> websocket).
	Upgrade
	// Noop does nothing; used to cleanly terminate a polling request.
	Noop
)

func (PacketType) String

func (t PacketType) String() string

String returns the lowercase Engine.IO name of the packet type (e.g. "message", "ping"), or "unknown" for an unrecognized value.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL