RESP parser with Buffer and cursor
We accumulate bytes in a per-connection Buffer. parseResp tries to read one frame from offset 0. Returns null if the buffer is incomplete. The connection waits for more data and retries.
Before the parser, meet the format itself. RESP is line-based: every token ends with \r\n. The first byte of a frame tells you its type. + is a simple string, so +OK\r\n means OK. $ is a bulk string with a byte-length prefix, so $3\r\nGET\r\n means the 3-byte string GET, and the special $-1\r\n means nil. * is an array with an element count, so a client sends GET foo as *2\r\n$3\r\nGET\r\n$3\r\nfoo\r\n: an array of two bulk strings. The parser below is just a switch on that first byte.
function parseResp(buf, offset = 0) {
if (offset >= buf.length) return null;
const typeByte = String.fromCharCode(buf[offset]);
const crlfIdx = buf.indexOf("\r\n", offset + 1);
if (crlfIdx < 0) return null;
const header = buf.slice(offset + 1, crlfIdx).toString("ascii");
const after = crlfIdx + 2;
switch (typeByte) {
case "+": return { value: header, consumed: after - offset };
case "$": {
const n = parseInt(header, 10);
if (n === -1) return { value: null, consumed: after - offset };
const endPayload = after + n;
if (buf.length < endPayload + 2) return null;
return { value: buf.slice(after, endPayload).toString("utf8"), consumed: endPayload + 2 - offset };
}
case "*": {
const count = parseInt(header, 10);
const items = [];
let cursor = after;
for (let i = 0; i < count; i++) {
const child = parseResp(buf, cursor);
if (child === null) return null;
items.push(child.value);
cursor += child.consumed;
}
return { value: items, consumed: cursor - offset };
}
}
}The whole parser. Cursor-based. Returns null when more bytes are needed.
The (value, consumed) shape lets each caller advance their cursor by exactly the right amount. Buffer.slice is a view (no copy), so this is fast.
Quiz: Quiz
Loading practice…