RESP parser with byteslice and tagged arrays

String#byteslice is byte-aware (not character-aware). For a binary protocol like RESP we always want bytes. The buffer is also created with Encoding::BINARY so it never tries to interpret bytes as UTF-8.

Before you read the parser, dissect one frame by hand. When redis-cli sends PING, the bytes on the wire are 1\r\n$4\r\nPING\r\n. The first byte announces an array and the 1 says it holds one element. \r\n (that is the CRLF constant in the code below) ends every header line. Then $ announces a bulk string, 4 is the payload length in bytes, \r\n ends that header, then come the four payload bytes PING and a closing \r\n. RESP has five type bytes: + simple string, - error, : integer, $ bulk string, * array. So every value is a type byte, a header line ended by \r\n, and for a bulk string a length-prefixed run of raw bytes. That is exactly what case t switches on: read the first byte, slice up to CRLF for the header, then for $ read header.to_i more bytes.

02-resp-parser/server.rb
ruby
def parse_resp(buf, cur)
  return [:incomplete, cur] if cur >= buf.bytesize
  t = buf.byteslice(cur, 1)
  crlf_idx = buf.index(CRLF, cur)
  return [:incomplete, cur] if crlf_idx.nil?
  header = buf.byteslice(cur + 1, crlf_idx - cur - 1)
  after = crlf_idx + 2
  case t
  when '$'
    n = header.to_i
    ep = after + n
    return [:incomplete, cur] if buf.bytesize < ep + 2
    [[:bulk, buf.byteslice(after, n)], ep + 2]
  when '*'
    n = header.to_i
    items = []
    c = after
    n.times do
      v, nc = parse_resp(buf, c)
      return [:incomplete, cur] if v == :incomplete
      items << v
      c = nc
    end
    [[:array, items], c]
  end
end

The RESP parser. case/when on the first byte, recursive on arrays.

Ruby has no enum. We tag tuples with a symbol: [:bulk, \u0022foo\u0022], [:int, 42], [:array, [...]]. Callers pattern-match on the first element. Less type-safe than Rust's enum but the structure is identical.

Quiz: Quiz

Loading practice…

AI prompt: Try it: hand-write a frame

Loading practice…