This protocol documentation is written for people who are interested in implementing a RobustIRC client.
In case you have any questions that this document leaves unanswered, please file an issue on GitHub so that we can answer your question and improve the document.
You can find the reference implementation of this protocol at https://github.com/robustirc/bridge/blob/master/robustsession/robustsession.go
1. Introduction
This document outlines how RobustIRC clients and RobustIRC servers communicate. In IRC, communication happens over a single long-lived TCP connection. RobustIRC uses HTTP requests instead, so there are requests to create a session (≘ connect), delete the session (≘ disconnect), send a message (≘ send) and receive a number of messages (≘ receive).
The RobustIRC client can send these requests to any server in the network. In
case the chosen server is not the raft leader, that server will proxy the
request to the current leader (read-only requests such as GetMessages can
be handled by raft followers as well). When a request was redirected, the
Content-Location
response header contains the current raft leader. Clients
SHOULD direct further requests directly to the server specified in
Content-Location
in order to decrease load on the network and minimize
latency. In case the chosen server cannot be reached or responds with an error,
the client can chose another server, for example at random, to retry the
request.
The behavior described in the previous paragraphs make this protocol resilient against network partitions and single-server unavailability.
2. Resolving hosts
RobustIRC mandates the use of an SRV
DNS record to discover servers. The client gets a network address, e.g.
robustirc.net
and resolves the _robustirc._tcp
SRV record, e.g.
_robustirc._tcp.robustirc.net
. The target:port tuples to which that SRV
record resolves are used as initial list of servers. robustPing messages in the
GetMessages response contain a list of servers so that clients don’t need to
re-resolve but still get an up-to-date list of servers.
The advantage of using SRV records is that the network address is short and
memorable, but RobustIRC servers can be hosted on different servers than the
network address itself. E.g., robustirc.net’s A record points to github, but
the _robustirc._tcp.robustirc.net
record points to 3 different servers.
Clients MAY ignore the priority and weight fields of the SRV record, but SHOULD randomly shuffle the list of servers (as opposed to always connecting to the first one).
3. Error handling
Each of the described Methods returns the HTTP 200 OK
status code when
successful or an HTTP error (e.g. 500 Internal Server Error
) when things went
wrong.
Each method (except for CreateSession) can return an HTTP 404 Not Found
status code when the specified session was not found, or a HTTP 400 Bad
Request
if something with the request itself was wrong. All 4xx replies are
permanent and clients SHOULD close the connection — no subsequent message with
that same session id will be accepted.
In case the server replies with an error, the response body will contain a human-readable error message that the client should expose.
4. Exponential backoff
To avoid overloading the network, clients MUST use exponential backoff, with an upper cap on retries of at least 60 seconds (e.g. \(2^6 = 64\)).
This means: the time between two unsuccessful requests to any specific server (e.g. GetMessages) must be at least \(2^0 = 1\) second. For the next request, this time will be increased to \(2^1 = 2\) seconds, then \(2^2 = 4\) seconds, etc.
Furthermore, clients MUST delay connecting to a different server for a random duration between 250ms to at least 500ms to avoid the thundering herd problem.
5. Methods
Implementations MUST set their User-Agent string to a clear identifier, such as
WeeChat RobustIRC plugin v1.0
.
5.1. CreateSession
- IRC equivalent
-
Creating a TCP connection to an IRC server (connect).
- HTTP request
-
POST /robustirc/v1/session
- Request body (JSON-encoded)
-
Empty.
- Response body (JSON-encoded)
-
{"Sessionid": "$sessionid", "Sessionauth": "$sessionauth", "Prefix": "$prefix"}
$prefix
is the IRC server prefix, e.g. robustirc.net
. The prefix is
provided so that clients which act as a proxy (e.g. the
RobustIRC bridge) can do their own
PING/PONG exchange and pretend to be the IRC server in order to not confuse
clients.
$sessionauth
needs to be sent in all further requests as the X-Session-Auth
header. Without this header, the server will refuse to handle your request to
prevent session stealing. We decided against making this a cookie because of
the domain scope restriction of cookies (i.e. all servers in a RobustIRC
network would need to be under a common domain).
5.2. DeleteSession
- IRC equivalent
-
Closing the TCP connection (disconnect).
- HTTP request
-
DELETE /robustirc/v1/$sessionid
- Request body (JSON-encoded)
-
{"Quitmessage": "$quitmsg"}
- Response body (JSON-encoded)
-
Empty.
$quitmsg
will be synthesized into a QUIT IRC message on the RobustIRC server.
5.3. PostMessage
- IRC equivalent
-
Sending a line.
- HTTP request
-
POST /robustirc/v1/$sessionid/message
- Request body (JSON-encoded)
-
{"Data": "$ircmessage", "ClientMessageId": $clientid}
- Response body (JSON-encoded)
-
Empty.
$ircmessage
must be an IRC protocol message as per
RFC2812, e.g. TOPIC #robustirc :hey
.
$ircmessage
must not contain the new line character (neither \n
nor \r
).
$ircmessage
must be encoded in valid UTF-8. Messages which are not valid
UTF-8 will be refused and an error will be returned.
$clientid
is an uint64 identifier created by the client. It SHOULD be chosen
randomly, being different across multiple instances of the client, e.g. by
using a random value + the hash of the message. Clients MUST not send multiple
requests in parallel, but wait for the reply after each request. If the reply
is an error (either a network failure or an HTTP 5xx error code), the client
SHOULD resend the request to a different server, with unchanged $clientid
.
$clientid
MUST be different between two subsequent messages.
When the server receives a message with the same $clientid
as the last
committed message (e.g. when the client retried because the connection broke
before the server’s response was transmitted), it will acknowledge the message
again without committing it again. This mechanism is used to prevent duplicate
messages when retrying.
Sessions are terminated by the server after a certain time period of inactivity
(30 minutes by default). In order to prevent that, the client should send a
PING
ircmessage after every minute of inactivity, where inactivity means
there was no PostMessage request.
5.4. GetMessages
- IRC equivalent
-
Reading a line.
- HTTP request
-
GET /robustirc/v1/$sessionid/messages?lastseen=$msgid
Example:GET /robustirc/v1/$sessionid/messages?lastseen=1426109846660183024.1
- Request body (JSON-encoded)
-
Empty.
- Response body (JSON-encoded)
-
The request body is a chunked HTTP response where each chunk is a JSON message. The following JSON messages are defined:
robustIRCToClient (=3) message spec:{"Type": 3, "Id": $msgid, "Data": "$ircmessage"}
robustIRCToClient example:{"Id":{"Id":1426109846660183024,"Reply":1}, "Type":3, "Data":":robustirc.net 001 michael :Welcome to RobustIRC!"}
robustPing (=4) message:{"Type": 4, "Servers": ["$server0", "$serverN"]}
This request must be done via at least HTTP 1.1, because the response will use HTTP chunked transfer encoding. Each chunk is one JSON array describing a message. There is no timeout on this request, so the connection may live for a long time.
Messages are guaranteed to be in chronological order.
$msgid
is a (structured) unique id for the message, about whose contents the
client should not make any assumptions. Clients can specify $msgid
in the
lastseen parameter, telling the server to only send messages newer than the
specified $msgid
. When specifying $msgid
, the format $id.$reply
must be
used, where $id
and $reply
represent the contents of the $msgid
’s Id and
Reply field, respectively.