@dhttp/protocol
v0.0.0-rc.0
Published
Wire protocol shared by a dhttp bridge and its clients.
Readme
@dhttp/protocol
Wire protocol shared by a dhttp bridge and its clients.
Everything both ends of a dhttp connection have to agree on: the command numbers, the wire encodings, and the framing helper. No transport, no policy, no HTTP.
npm install @dhttp/protocolConventions
Two conventions carry the whole protocol:
- A command's payload is a
requestframe — method, path, headers, body.WEBSOCKETsends its one as the first frame of the request stream, sincebare-rpclets a request carry either inline data or a stream, not both. - A response is a stream whose first frame is a
head— status, status message, headers — and whose remaining frames are the body: chunks for a request, whole messages for a WebSocket. Refusals and gateway errors are just a head with a 4xx or 5xx and a JSON body, so there is one response shape rather than one per outcome.
INFO is the exception: it has no request frame and replies with a plain
value, because it is asked before anything else is agreed.
API
const { PROTOCOL, commands, messages, first } = require('@dhttp/protocol')PROTOCOL
Protocol version string, reported by INFO. Currently 'dhttp/1'. Bumped only
on a breaking change to the commands or their encodings.
commands
Command ids. The numbers are part of the wire format: only ever append, never renumber.
| Command | Id | Meaning |
| ----------- | --- | ------------------------------------------------------------------------------ |
| INFO | 1 | Ask who the peer is. The only command whose reply is a plain value. |
| REQUEST | 2 | One HTTP round trip. |
| WEBSOCKET | 3 | A WebSocket to the upstream, terminated by the bridge and relayed as messages. |
messages
compact-encoding codecs for the three payloads.
messages.request—{ method, path, headers, body }. Headers travel as a flat list of alternating name/value strings, so repeated headers (set-cookie) survive the round trip. An absent body is zero bytes on the wire andnullin JavaScript.messages.head—{ status, statusMessage, headers }. The first frame of every response; the body follows as its own frames, so there is nothing here about length or framing.messages.info— the service description, encoded as JSON so new fields can be added without breaking older clients.
const c = require('compact-encoding')
const buffer = c.encode(messages.request, {
method: 'GET',
path: '/v1/info',
headers: [['accept', 'application/json']],
body: null
})await first(stream)
Pull exactly one frame off a stream without putting it into flowing mode, so
whoever reads next still receives every frame that follows. Used to take the
head off a response before handing the rest to the caller.
const head = c.decode(messages.head, await first(body))Rejects if the stream errors, or closes before a frame arrives.
License
Apache-2.0
