Engine.IO protocol
https://github.com/socketio/engine.io-protocol.git
This document describes the 4th version of the Engine.IO protocol.
Table of content
The Engine.IO protocol enables full-duplex#FULL-DUPLEX) and low-overhead communication between a client and a server.
It is based on the WebSocket protocol and uses HTTP long-polling as fallback if the WebSocket connection can't be established.
The reference implementation is written in TypeScript:
The connection between an Engine.IO client and an Engine.IO server can be established with:
The HTTP long-polling transport (also simply referred as "polling") consists of successive HTTP requests:
GET requests, for receiving data from the serverPOST requests, for sending data to the serverThe path of the HTTP requests is /engine.io/ by default.
It might be updated by libraries built on top of the protocol (for example, the Socket.IO protocol uses /socket.io/).
The following query parameters are used:
| Name | Value | Description |
|---|---|---|
EIO | 4 | Mandatory, the version of the protocol. |
transport | polling | Mandatory, the name of the transport. |
| sid | <sid> | Mandatory once the session is established, the session identifier. |
If a mandatory query parameter is missing, then the server MUST respond with an HTTP 400 error status.
When sending binary data, the sender (client or server) MUST include a Content-Type: application/octet-stream header.
Without an explicit Content-Type header, the receiver SHOULD infer that the data is plaintext.
Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
To send some packets, a client MUST create an HTTP POST request with the packets encoded in the request body:
CLIENT SERVER
โ โ
โ POST /engine.io/?EIO=4&transport=polling&sid=... โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ HTTP 200 โ
โ โ
The server MUST return an HTTP 400 response if the session ID (from the sid query parameter) is not known.
To indicate success, the server MUST return an HTTP 200 response, with the string ok in the response body.
To ensure packet ordering, a client MUST NOT have more than one active POST request. Should it happen, the server MUST return an HTTP 400 error status and close the session.
To receive some packets, a client MUST create an HTTP GET request:
CLIENT SERVER
โ GET /engine.io/?EIO=4&transport=polling&sid=... โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ . โ
โ . โ
โ . โ
โ . โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ HTTP 200 โ
The server MUST return an HTTP 400 response if the session ID (from the sid query parameter) is not known.
The server MAY not respond right away if there are no packets buffered for the given session. Once there are some packets to be sent, the server SHOULD encode them (see Packet encoding) and send them in the response body of the HTTP request.
To ensure packet ordering, a client MUST NOT have more than one active GET request. Should it happen, the server MUST return an HTTP 400 error status and close the session.
The WebSocket transport consists of a WebSocket connection, which provides a bidirectional and low-latency communication channel between the server and the client.
The following query parameters are used:
| Name | Value | Description |
|---|---|---|
EIO | 4 | Mandatory, the version of the protocol. |
transport | websocket | Mandatory, the name of the transport. |
| sid | <sid> | Optional, depending on whether it's an upgrade from HTTP long-polling or not. |
If a mandatory query parameter is missing, then the server MUST close the WebSocket connection.
Each packet (read or write) is sent its own WebSocket frame.
A client MUST NOT open more than one WebSocket connection per session. Should it happen, the server MUST close the WebSocket connection.
An Engine.IO packet consists of:
| Type | ID | Usage |
|---|---|---|
| open | 0 | Used during the handshake. |
To establish a connection, the client MUST send an HTTP GET request to the server:
CLIENT SERVER
โ โ
โ GET /engine.io/?EIO=4&transport=polling โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ HTTP 200 โ
โ โ
CLIENT SERVER
โ โ
โ GET /engine.io/?EIO=4&transport=websocket โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ HTTP 101 โ
โ โ
If the server accepts the connection, then it MUST respond with an open packet with the following JSON-encoded payload:
| Key | Type | Description |
|---|---|---|
sid | string | The session ID. |
upgrades | string[] | The list of available transport upgrades. |
pingInterval | number | The ping interval, used in the heartbeat mechanism (in milliseconds). |
pingTimeout | number | The ping timeout, used in the heartbeat mechanism (in milliseconds). |
maxPayload | number | The maximum number of bytes per chunk, used by the client to aggregate packets into payloads. |
{
"sid": "lv_VI97HAXpY6yYWAAAC",
"upgrades": ["websocket"],
"pingInterval": 25000,
"pingTimeout": 20000,
"maxPayload": 1000000
}
The client MUST send the sid value in the query parameters of all subsequent requests.
Once the handshake is completed, a heartbeat mechanism is started to check the liveness of the connection:
CLIENT SERVER
โ *** Handshake *** โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ 2 โ (ping packet)
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ 3 โ (pong packet)
At a given interval (the pingInterval value sent in the handshake) the server sends a ping packet and the client has a few seconds (the pingTimeout value) to send a pong packet back.
If the server does not receive a pong packet back, then it SHOULD consider that the connection is closed.
Conversely, if the client does not receive a ping packet within pingInterval + pingTimeout, then it SHOULD consider that the connection is closed.
By default, the client SHOULD create an HTTP long-polling connection, and then upgrade to better transports if available.
To upgrade to WebSocket, the client MUST:
ping packet with the string probe in the payloadnoop packet to any pending GET request (if applicable) to cleanly close HTTP long-polling transportpong packet with the string probe in the payloadupgrade packet to complete the upgrade:
CLIENT SERVER
โ โ
โ GET /engine.io/?EIO=4&transport=websocket&sid=... โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ HTTP 101 (WebSocket handshake) โ
โ โ
โ ----- WebSocket frames ----- โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ 2probe โ (ping packet)
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ 3probe โ (pong packet)
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ 5 โ (upgrade packet)
โ โ
Once the handshake is completed, the client and the server can exchange data by including it in a message packet.
The serialization of an Engine.IO packet depends on the type of the payload (plaintext or binary) and on the transport.
The character encoding is UTF-8 for plain text and for base64-encoded binary payloads.
Due to the nature of the HTTP long-polling transport, multiple packets might be concatenated in a single payload in order to increase throughput.
Format:
<packet type>[<data>]<separator><packet type>[<data>]<separator><packet type>[<data>][...]
Example:
4hello\x1e2\x1e4world
with:
4 => message packet type
hello => message payload
\x1e => separator
2 => ping packet type
\x1e => separator
4 => message packet type
world => message payload
The packets are separated by the record separator character: \x1e
Binary payloads MUST be base64-encoded and prefixed with a b character:
Example:
4hello\x1ebAQIDBA==
with:
4 => message packet type
hello => message payload
\x1e => separator
b => binary prefix
AQIDBA== => buffer <01 02 03 04> encoded as base64
The client SHOULD use the maxPayload value sent during the handshake to decide how many packets should be concatenated.
Each Engine.IO packet is sent in its own WebSocket frame.
Format:
<packet type>[<data>]
Example:
4hello
with:
4 => message packet type
hello => message payload (UTF-8 encoded)
Binary payloads are sent as is, without modification.
v0.9 and below.
The 3rd version of the protocol is used in Socket.IO v1 and v2.
Please note that this only applies to HTTP long-polling. Binary data is sent in WebSocket frames with no additional transformation.
\x1e) instead of counting of charactersFor example, โฌ was encoded to 2:4โฌ, though Buffer.byteLength('โฌ') === 3.
Note: this assumes the record separator is not used in the data.
The 4th version (current) is included in Socket.IO v3 and above.
The test suite in the test-suite/ directory lets you check the compliance of a server implementation.
Usage:
npm ci && npm testindex.html file in your browserimport { listen } from "engine.io";
const server = listen(3000, {
pingInterval: 300,
pingTimeout: 200,
maxPayload: 1e6,
cors: {
origin: "*"
}
});
server.on("connection", socket => {
socket.on("data", (...args) => {
socket.send(...args);
});
});