RESP: The Redis wire format

Once your server speaks RESP, every Redis client works against it. redis-cli, redis-py, the Go and Java drivers. The compatibility comes from one tiny grammar.

RESP grammar

Every frame starts with a type byte. CRLF ends every line. Length prefixes make bulk strings binary-safe.
wire
text
*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$5\r\nhello\r\n

*3        array of 3
$3 SET    bulk string of length 3, "SET"
$3 foo    bulk string of length 3, "foo"
$5 hello  bulk string of length 5, "hello"

What 'SET foo hello' actually looks like on the wire. Three bulk strings inside an array.

02-resp-parser/server.py
python
def parse_resp(buf):
    if not buf: return None
    type_byte = buf[0:1]
    end = buf.find(CRLF)
    if end < 0: return None
    header = buf[1:end].decode("ascii")
    after = end + 2

    if type_byte == b"+": return header, after
    if type_byte == b"-": return Exception(header), after
    if type_byte == b":": return int(header), after
    if type_byte == b"$":
        n = int(header)
        if n == -1: return None, after
        end_payload = after + n
        if len(buf) < end_payload + 2: return None
        return buf[after:end_payload].decode("utf-8"), end_payload + 2
    if type_byte == b"*":
        count = int(header)
        if count == -1: return None, after
        items, cursor = [], after
        for _ in range(count):
            r = parse_resp(buf[cursor:])
            if r is None: return None
            v, c = r
            items.append(v); cursor += c
        return items, cursor

The whole parser. Recursive for arrays. Returns None when the buffer is incomplete so the read loop knows to wait for more bytes.

TCP gives you a byte stream, not messages. recv() might return half a frame. The parser must handle that. We return None when the buffer is incomplete. The main loop reads more bytes and retries. Same pattern shows up in every networked system.

Ordering exercise: Order the steps to parse one RESP frame

Loading practice…

Quiz: Quiz

Loading practice…