5 min read
Mapping RPCs to streams
Understand how requests and responses are sent over icerpc.
A stream per RPC
The QUIC transport and its multiplexed transport abstraction are ideal for RPCs and the icerpc protocol takes full advantage of these transports.
icerpc creates a dedicated multiplexed stream for each RPC. A two-way RPC, with a request and a response, is carried by a bidirectional stream, while a one-way RPC, with a request and no response, is carried by an unidirectional stream.
A request flows from the endpoint that created the stream to the endpoint that accepted the stream. A response flows the other way—from the endpoint that accepted the stream to the endpoint that created the stream.
Request layout
An icerpc request consists of a header followed by a payload. As far as icerpc is concerned, the payload is just a stream of bytes with an unknown size. The end of the multiplexed stream marks the end of the payload.
The request header holds:
- the path of the service
- the operation name
- request fields
icerpc transmits the request fields without attaching any meaning to their values or presence.
The request header is specified using Slice:
compact struct Request { headerSize: varuint62 header: RequestHeader payload: ...bytes...}
compact struct RequestHeader { path: string operation: string fields: Dictionary<RequestFieldKey, Sequence<uint8>>}
For example, a request for operation "op" at path "/foo" with an empty payload and no field can be encoded as:
0x25 0x00 : header size (9) on 2 bytes, little endian0x10 : path size (4) on 1 byte0x2F 0x66 0x6F 0x6F : UTF-8 encoding of "/foo"0x08 : operation size (2) on 1 byte0x6F 0x70 : UTF-8 encoding of "op"0x00 : field dictionary size (0) on 1 byte (no fields)
With Slice's varuint62 encoding, the first 2 bits of the first byte encode the number of bytes used to encode the value. As a result, for a varuint62 encoded on a single byte, the encoded value is source * 4
, and for a varuint62 encoded on 2 bytes, the encoded value is source * 4 + 1
.
Response layout
An icerpc response consists of a header followed by a payload. The payload of a response is just like the payload of a request: a stream of bytes with an unknown size. The response payload ends when the multiplexed stream ends.
The response header holds:
- a status code
- an error message when the status code is not
Ok
- response fields
icerpc transmits the response fields without attaching any meaning to their values or presence.
The response header is specified in Slice:
compact struct Response { headerSize: varuint62 header: ResponseHeader payload: ...bytes...}
compact struct ResponseHeader { statusCode: StatusCode errorMessage: string // only present when statusCode is not `Ok` fields: Dictionary<ResponseFieldKey, Sequence<uint8>>}
For example, a response with status code Ok
, no fields and an empty payload can be encoded as:
0x09 0x00 : header size (2) on 2 bytes, little endian0x00 : status code (0) on 1 byte0x00 : field dictionary size (0) on 1 byte (no fields)