๐Ÿ“ฆ socketio / engine.io-protocol

Engine.IO protocol

โ˜… 248 stars โ‘‚ 46 forks ๐Ÿ‘ 248 watching
javascriptnodejswebsocket
๐Ÿ“ฅ Clone https://github.com/socketio/engine.io-protocol.git
HTTPS git clone https://github.com/socketio/engine.io-protocol.git
SSH git clone git@github.com:socketio/engine.io-protocol.git
CLI gh repo clone socketio/engine.io-protocol
Damien Arrachequesne Damien Arrachequesne docs: update test suite ddf922a 3 years ago ๐Ÿ“ History
๐Ÿ“‚ ddf922aa9ee52c3fa64d9a0232b6168555907cf6 View all commits โ†’
๐Ÿ“ test-suite
๐Ÿ“„ README.md
๐Ÿ“„ README.md

Engine.IO Protocol

This document describes the Engine.IO protocol. For a reference JavaScript implementation, take a look at engine.io-parser, engine.io-client and engine.io.

Table of Contents:

Revision

This is revision 4 of the Engine.IO protocol.

The revision 2 can be found here: https://github.com/socketio/engine.io-protocol/tree/v2

The revision 3 can be found here: https://github.com/socketio/engine.io-protocol/tree/v3

Anatomy of an Engine.IO session

  • Transport establishes a connection to the Engine.IO URL.
  • Server responds with an open packet with JSON-encoded handshake data:
  • sid session id (String)
  • upgrades possible transport upgrades (Array of String)
  • pingTimeout server configured ping timeout, used for the client
to detect that the server is unresponsive (Number)
  • pingInterval server configured ping interval, used for the client
to detect that the server is unresponsive (Number)
  • maxPayload server configured maximum number of bytes per chunk, used by the client to aggregate packets into payloads (Number)
  • Client must respond to periodic ping packets sent by the server
with pong packets.
  • Client and server can exchange message packets at will.
  • Polling transports can send a close packet to close the socket, since
they're expected to be "opening" and "closing" all the time.

Sample session

  • Request nยฐ1 (open packet)
GET /engine.io/?EIO=4&transport=polling&t=N8hyd6w
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
0{"sid":"lv_VI97HAXpY6yYWAAAC","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000,"maxPayload":1000000}

Details:

0           => "open" packet type
{"sid":...  => the handshake data

Note: the t query param is used to ensure that the request is not cached by the browser.

  • Request nยฐ2 (message in):
socket.send('hey') is executed on the server:

GET /engine.io/?EIO=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
4hey

Details:

4           => "message" packet type
hey         => the actual message

Note: the sid query param contains the sid sent in the handshake.

  • Request nยฐ3 (message out)
socket.send('hello'); socket.send('world'); is executed on the client:

POST /engine.io/?EIO=4&transport=polling&t=N8hzxke&sid=lv_VI97HAXpY6yYWAAAC
> Content-Type: text/plain; charset=UTF-8
4hello\x1e4world
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
ok

Details:

4           => "message" packet type
hello       => the 1st message
\x1e        => separator
4           => "message" message type
world       => the 2nd message

  • Request nยฐ4 (WebSocket upgrade)
GET /engine.io/?EIO=4&transport=websocket&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 101 Switching Protocols

WebSocket frames:

< 2probe    => probe request
> 3probe    => probe response
< 5         => "upgrade" packet type
> 4hello    => message (not concatenated)
> 4world
> 2         => "ping" packet type
< 3         => "pong" packet type
> 1         => "close" packet type

Sample session with WebSocket only

In that case, the client only enables WebSocket (without HTTP polling).

GET /engine.io/?EIO=4&transport=websocket
< HTTP/1.1 101 Switching Protocols

WebSocket frames:

< 0{"sid":"lv_VI97HAXpY6yYWAAAC","pingInterval":25000,"pingTimeout":5000,"maxPayload":1000000}} => handshake
< 4hey
> 4hello    => message (not concatenated)
> 4world
< 2         => "ping" packet type
> 3         => "pong" packet type
> 1         => "close" packet type

URLs

An Engine.IO url is composed as follows:

/engine.io/[?<query string>]

  • The engine.io pathname should only be changed by higher-level
frameworks whose protocol sits on top of engine's.

  • The query string is optional and has six reserved keys:
  • transport: indicates the transport name. Supported ones by default are
polling, websocket.
  • j: if the transport is polling but a JSONP response is required, j
must be set with the JSONP response index.
  • sid: if the client has been given a session id, it must be included
in the querystring.
  • EIO: the version of the protocol
  • t: a hashed-timestamp used for cache-busting
FAQ: Is the /engine.io portion modifiable?

Provided the server is customized to intercept requests under a different path segment, yes.

FAQ: What determines whether an option is going to be part of the path versus being encoded as part of the query string? In other words, why is the transport not part of the URL?

It's convention that the path segments remain only that which allows to disambiguate whether a request should be handled by a given Engine.IO server instance or not. As it stands, it's only the Engine.IO prefix (/engine.io) and the resource (default by default).

Encoding

There's two distinct types of encodings

  • packet
  • payload

Packet

An encoded packet can be UTF-8 string or binary data. The packet encoding format for a string is as follows

<packet type id>[<data>]
example:
4hello

For binary data the packet type is not included, since only "message" packet type can include binary.

0 open

Sent from the server when a new transport is opened (recheck)

1 close

Request the close of this transport but does not shutdown the connection itself.

2 ping

Sent by the server. Client should answer with a pong packet.

example

  • server sends: ``2%%CODEBLOCK13%%3%%CODEBLOCK14%%4HelloWorld%%CODEBLOCK15%%socket.on('message', function (data) { console.log(data); });%%CODEBLOCK16%%4HelloWorld%%CODEBLOCK17%%socket.on('message', function (data) { console.log(data); });%%CODEBLOCK18%%2probe%%CODEBLOCK19%%3probe%%CODEBLOCK20%%5%%CODEBLOCK21%% <packet1>\x1e<packet2>\x1e<packet3> %%CODEBLOCK22%% <packet1>\x1eb<packet2 data in b64>[...] %%CODEBLOCK23%% [ { "type": "message", "data": "hello" }, { "type": "message", "data": "โ‚ฌ" } ] %%CODEBLOCK24%% 4hello\x1e4โ‚ฌ %%CODEBLOCK25%% [ { "type": "message", "data": "โ‚ฌ" }, { "type": "message", "data": buffer <01 02 03 04> } ] %%CODEBLOCK26%% 4โ‚ฌ\x1ebAQIDBA== with 4 => "message" packet type โ‚ฌ \x1e => record separator b => indicates a base64 packet AQIDBA== => buffer content encoded in base64 %%CODEBLOCK27%% ___eio <encoded payload> "" rel="noopener"> <j> ; %%CODEBLOCK28%% ___eio[4]("packet data"); %%CODEBLOCK29%% d=<escaped packet payload> %%CODEBLOCK30%%js import { 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); }); }); ``