RESP parser with bufio.Reader

bufio.Reader wraps net.Conn and gives us byte-level and line-level reads with internal buffering. Exactly the primitives a length-prefixed protocol needs.

Before the parser, dissect one frame by hand. The bytes a client sends for PING are 1\r\n$4\r\nPING\r\n. The first byte announces an array, the 1 says it has one element, and \r\n ends that header line. Next, $ announces a bulk string, 4 says the payload is four bytes, \r\n ends the header, then come the four payload bytes PING and a closing \r\n. Five type bytes cover all of RESP: + simple string, - error, : integer, $ bulk string, * array. Every value is a type byte, a header line ended by \r\n, and for bulk strings a length-prefixed payload. That is exactly the switch the parser below performs.

02-resp-parser/main.go
go
func ParseRESP(r *bufio.Reader) (any, error) {
    typeByte, err := r.ReadByte()
    if err != nil { return nil, err }
    line, err := readLine(r)
    if err != nil { return nil, err }
    switch typeByte {
    case '+': return line, nil
    case '-': return errors.New(line), nil
    case ':': return strconv.Atoi(line)
    case '$':
        n, _ := strconv.Atoi(line)
        if n == -1 { return nil, nil }
        buf := make([]byte, n+2)
        io.ReadFull(r, buf)
        return string(buf[:n]), nil
    case '*':
        count, _ := strconv.Atoi(line)
        items := make([]any, count)
        for i := range items { items[i], _ = ParseRESP(r) }
        return items, nil
    }
    return nil, fmt.Errorf("unknown type byte %q", typeByte)
}

The whole RESP parser in ~40 lines. Recursive for arrays. Returns any for heterogeneous values (string, int, []any, nil, error).

Go does not have native sum types. We use any (formerly interface{}) for the heterogeneous return. Callers type-switch on it. For a production parser you would define a typed RESPValue struct with a discriminator. The exercises walk through that refactor.

Quiz: Quiz

Loading practice…

AI prompt: Try it: hand-write a frame

Loading practice…