Documentation
¶
Overview ¶
Package resp implements the Redis Serialization Protocol in both its RESP2 and RESP3 variants. It is a pure codec: it transforms byte buffers and has no dependency on net or on any other aki package, so the encoder and decoder can be fuzzed and unit-tested against in-memory buffers without a socket. The networking layer drives this package over a real connection.
The byte-level contract is doc 06 of the aki specification. Every framing decision here is made to be byte-identical to Redis so a client written for Redis talks to aki unchanged.
Index ¶
- Constants
- Variables
- func FormatDouble(f float64) string
- func ParseInline(buf []byte, pos int) ([][]byte, int, error)
- func ParseRequest(buf []byte, pos int, maxBulkLen int64) ([][]byte, int, error)
- type Encoder
- func (e *Encoder) BeginStreamedBulkString()
- func (e *Encoder) EndStreamedBulkString()
- func (e *Encoder) Proto() int
- func (e *Encoder) SetProto(proto int)
- func (e *Encoder) WriteArrayLen(n int)
- func (e *Encoder) WriteAttributeLen(n int)
- func (e *Encoder) WriteBigNumber(n *big.Int)
- func (e *Encoder) WriteBool(b bool)
- func (e *Encoder) WriteBulkError(errStr string)
- func (e *Encoder) WriteBulkString(data []byte)
- func (e *Encoder) WriteBulkStringStr(s string)
- func (e *Encoder) WriteChunk(data []byte)
- func (e *Encoder) WriteDouble(f float64)
- func (e *Encoder) WriteError(errStr string)
- func (e *Encoder) WriteInteger(n int64)
- func (e *Encoder) WriteMapLen(n int)
- func (e *Encoder) WriteNull()
- func (e *Encoder) WriteNullArray()
- func (e *Encoder) WritePushLen(n int)
- func (e *Encoder) WriteRaw(p []byte)
- func (e *Encoder) WriteSetLen(n int)
- func (e *Encoder) WriteStatus(s string)
- func (e *Encoder) WriteVerbatimString(enc string, data []byte)
- type ProtocolError
- type RESPType
- type RESPValue
- type Writer
Constants ¶
const ( // MaxMultibulkLen is the largest element count accepted in a client // multibulk request (1M). MaxMultibulkLen = 1024 * 1024 // DefaultMaxBulkLen is the default proto-max-bulk-len (512 MiB): the largest // single bulk argument accepted. DefaultMaxBulkLen = 512 * 1024 * 1024 // MaxInlineLen is the largest inline request line, including the newline // (64 KiB). MaxInlineLen = 64 * 1024 )
Protocol limits from doc 06 §6.4. The multibulk and inline caps are fixed; the bulk and query-buffer caps are defaults the networking layer may override from configuration.
Variables ¶
var ( ReplyOK = []byte("+OK\r\n") ReplyPong = []byte("+PONG\r\n") ReplyQueued = []byte("+QUEUED\r\n") ReplyNil2 = []byte("$-1\r\n") ReplyNil3 = []byte("_\r\n") ReplyNilArray2 = []byte("*-1\r\n") ReplyEmptyArray = []byte("*0\r\n") ReplyEmptyMap3 = []byte("%0\r\n") ReplyEmptySet3 = []byte("~0\r\n") ReplyZero = []byte(":0\r\n") ReplyOne = []byte(":1\r\n") ReplyFalse3 = []byte("#f\r\n") ReplyTrue3 = []byte("#t\r\n") ReplyReset = []byte("+RESET\r\n") // ReplyMaxClients is written to a connection that is accepted only to be // rejected because the server is at its maxclients limit (doc 19 §1.4). ReplyMaxClients = []byte("-ERR max number of clients reached\r\n") )
Pre-built replies for the most common cases, kept as package-level byte slices so the hot path can write them with a single copy and no per-request allocation (doc 06 §11.6). The networking layer writes these directly into a client's output buffer; callers must never mutate the returned slices.
var ErrNeedMore = errors.New("need more data")
ErrNeedMore signals that the buffer does not yet hold a complete value. The decoder never advances its position when returning ErrNeedMore, so the caller can read more bytes from the socket and retry from the same offset.
Functions ¶
func FormatDouble ¶
FormatDouble renders a float for the wire: the special values become inf, -inf, nan, and finite values use the shortest representation that round-trips through float64 (doc 06 §11.5).
func ParseInline ¶
ParseInline parses one inline command line from buf starting at pos: the telnet-friendly form where a human types space-separated words instead of a multibulk frame (doc 06 §5). It returns the argument vector, the position past the terminating newline, and an error. ErrNeedMore means no complete line is buffered yet. A ProtocolError ("too big inline request" or "unbalanced quotes in request") is fatal and the connection is closed.
The parser strips a trailing CR (so both \n and \r\n terminate), splits on runs of whitespace, and honours single and double quoting with the same escape rules redis-cli uses.
func ParseRequest ¶
ParseRequest extracts one client command from a query buffer. Client commands arrive either as a multibulk frame (every real client library) or as an inline line (a human on telnet); the first non-blank byte selects the path: '*' means multibulk, anything else means inline (doc 06 §5.5, §6).
It returns the argument vector (argv[0] is the command name), the position just past the consumed bytes, and an error. ErrNeedMore means the buffer does not yet hold a complete command and pos is returned unchanged, so the read loop can append more bytes and call again from the same offset. A ProtocolError is fatal: the read loop sends it and closes the connection.
A blank line (a lone CRLF or LF, which telnet clients send) is consumed and reported as an empty argv with a nil error; the caller skips it and retries.
maxBulkLen caps a single bulk argument (proto-max-bulk-len); pass DefaultMaxBulkLen for the default 512 MiB.
Types ¶
type Encoder ¶
type Encoder struct {
// contains filtered or unexported fields
}
Encoder serializes in-memory reply values to RESP. It carries the connection's negotiated protocol version (2 or 3) and chooses the wire shape accordingly: the command layer builds the same logical reply either way and the encoder is the single place the version is observed (doc 06 §4.5).
func NewEncoder ¶
NewEncoder returns an Encoder writing to w in the given protocol version. proto must be 2 or 3; any other value is treated as 2.
func (*Encoder) BeginStreamedBulkString ¶
func (e *Encoder) BeginStreamedBulkString()
BeginStreamedBulkString opens a chunked bulk string whose total length is not known in advance ($?). Follow with WriteChunk calls and close with EndStreamedBulkString.
func (*Encoder) EndStreamedBulkString ¶
func (e *Encoder) EndStreamedBulkString()
EndStreamedBulkString closes a streamed bulk string with the zero-length terminator chunk.
func (*Encoder) SetProto ¶
SetProto switches the encoder to a new protocol version, as HELLO does mid-connection. proto other than 3 is normalized to 2.
func (*Encoder) WriteArrayLen ¶
WriteArrayLen writes an array header. The caller then writes exactly n elements. Arrays are identical in RESP2 and RESP3.
func (*Encoder) WriteAttributeLen ¶
WriteAttributeLen writes an attribute (metadata) header for n pairs. The caller then writes n key-value pairs followed by the actual reply. In RESP2 attributes have no representation, so nothing is written and the caller's following reply stands alone.
func (*Encoder) WriteBigNumber ¶
WriteBigNumber writes an arbitrary-precision integer. RESP3 uses the ( type; RESP2 downgrades to a bulk string of the decimal digits.
func (*Encoder) WriteBool ¶
WriteBool writes a boolean. RESP3 uses the dedicated #t / #f type; RESP2 downgrades to the integers 1 and 0, matching how Redis reports EXPIRE, SISMEMBER, and the like.
func (*Encoder) WriteBulkError ¶
WriteBulkError writes an error that may contain newlines or exceed the inline limit. RESP3 uses the length-prefixed ! type for such errors; otherwise it falls back to a simple error.
func (*Encoder) WriteBulkString ¶
WriteBulkString writes a binary-safe bulk string ($). Valid and identical in both protocol versions.
func (*Encoder) WriteBulkStringStr ¶
WriteBulkStringStr is WriteBulkString for a Go string, avoiding a []byte conversion on the caller's side.
func (*Encoder) WriteChunk ¶
WriteChunk writes one chunk of a streamed bulk string.
func (*Encoder) WriteDouble ¶
WriteDouble writes a floating-point value. RESP3 uses the , type; RESP2 downgrades to a bulk string carrying the same decimal text, matching ZSCORE.
func (*Encoder) WriteError ¶
WriteError writes a simple error (-). The string is "PREFIX message" and must not contain CR or LF. Simple errors are used in both RESP2 and RESP3 for the ordinary error path.
func (*Encoder) WriteInteger ¶
WriteInteger writes an integer (:). Identical in RESP2 and RESP3. Small values come from the pre-rendered pool.
func (*Encoder) WriteMapLen ¶
WriteMapLen writes a map header for n key-value pairs. RESP3 uses the % type; RESP2 downgrades to a flat array of 2n elements, the shape HGETALL has always had on RESP2.
func (*Encoder) WriteNull ¶
func (e *Encoder) WriteNull()
WriteNull writes a nil reply. RESP3 collapses null bulk string and null array into the single _ type; RESP2 uses the null bulk string $-1.
func (*Encoder) WriteNullArray ¶
func (e *Encoder) WriteNullArray()
WriteNullArray writes a nil aggregate reply. RESP3 uses _; RESP2 uses the null array *-1 (e.g. a BLPOP timeout).
func (*Encoder) WritePushLen ¶
WritePushLen writes an out-of-band push header for n elements. RESP3 uses the > type; RESP2 downgrades to a plain array, which is how pub/sub messages have always been delivered on RESP2.
func (*Encoder) WriteRaw ¶
WriteRaw writes pre-framed bytes verbatim, the path used for the pooled static replies. The bytes must already be a complete, correctly framed RESP value.
func (*Encoder) WriteSetLen ¶
WriteSetLen writes a set header for n elements. RESP3 uses the ~ type; RESP2 downgrades to a plain array.
func (*Encoder) WriteStatus ¶
WriteStatus writes a simple string (+). Simple strings are identical in RESP2 and RESP3. s must not contain CR or LF.
func (*Encoder) WriteVerbatimString ¶
WriteVerbatimString writes a verbatim string with a 3-byte content-type hint (e.g. "txt", "mkd"). RESP3 uses the = type; RESP2 downgrades to a plain bulk string, dropping the hint. enc must be exactly three bytes.
type ProtocolError ¶
type ProtocolError struct{ Msg string }
ProtocolError is a fatal decode error: the bytes are not valid RESP and the connection must be closed after the error string is sent to the client. Its Error string is already prefixed so it can be written straight to the wire as a RESP simple error.
func ErrProtocol ¶
func ErrProtocol(msg string) ProtocolError
ErrProtocol builds a ProtocolError with the given description.
func (ProtocolError) Error ¶
func (e ProtocolError) Error() string
type RESPType ¶
type RESPType int
RESPType identifies a decoded value by its RESP leading byte. The constant values are the leading bytes themselves, so a decoder can switch on the byte read from the wire and a debugger shows a readable character.
const ( // RESP2 base types, valid on every connection regardless of version. TypeSimpleString RESPType = '+' TypeError RESPType = '-' TypeInteger RESPType = ':' TypeBulkString RESPType = '$' TypeArray RESPType = '*' // RESP3 additions, sent only after a successful HELLO 3 but always accepted // from the wire for protocol symmetry. TypeNull RESPType = '_' TypeBool RESPType = '#' TypeDouble RESPType = ',' TypeBigNumber RESPType = '(' TypeBulkError RESPType = '!' TypeVerbatim RESPType = '=' TypeMap RESPType = '%' TypeSet RESPType = '~' TypeAttribute RESPType = '|' TypePush RESPType = '>' )
type RESPValue ¶
type RESPValue struct {
Type RESPType
Str []byte // simple string, bulk string, verbatim string payload
Integer int64 // integer
Float float64 // double
BigInt *big.Int // big number
Bool bool // boolean
Err string // simple error or bulk error message
VerbEnc string // 3-char encoding prefix of a verbatim string
Elems []RESPValue // array, set, push elements
Map [][2]RESPValue
Attrs [][2]RESPValue // attribute metadata pairs (Type == TypeAttribute)
AttrBody *RESPValue // the reply an attribute is attached to
IsNull bool // null bulk string, null array, or RESP3 null
}
RESPValue is a tagged union of every value the decoder can produce. Only the fields relevant to Type carry meaning; the rest are zero. It mirrors the shape Redis clients expect and is the type the aki cli decodes server replies into.
func Decode ¶
Decode reads one complete RESP value from buf starting at pos and returns the value, the position just past it, and an error. The two error classes are load-bearing: ErrNeedMore means the buffer is incomplete and pos is returned unchanged so the caller can retry after reading more; a ProtocolError means the bytes are invalid and the connection must be closed. Decode handles all RESP2 and RESP3 types and is used both by the server to parse replies in tests and by the aki cli to decode server output (doc 06 §12).
type Writer ¶
type Writer interface {
WriteString(s string) (int, error)
WriteByte(b byte) error
Write(p []byte) (int, error)
}
Writer is the sink the encoder writes framed bytes into. The networking layer satisfies it with a client's reply buffer; tests satisfy it with a bytes.Buffer. Encoding methods do not return errors: they accumulate into the buffer, and the single flush to the socket is where a write error surfaces.