not working...

This commit is contained in:
Max Lerebourg
2022-10-19 08:22:00 +02:00
parent a197194591
commit f94e48aa03
3 changed files with 375 additions and 113 deletions
+375
View File
@@ -0,0 +1,375 @@
package redis
// My intepretation of the RESP protocol.
// Referenced from https://redis.io/docs/reference/protocol-spec/
// 8-7-2022
import (
"errors"
"fmt"
"io"
"log"
"net/textproto"
"net"
"bufio"
"strings"
"sync"
)
type RespT byte
const (
SimpleString RespT = '+'
Error RespT = '-'
Integer RespT = ':'
BulkString RespT = '$'
Array RespT = '*'
)
type Message interface {
String() string
}
type MsgSimpleStr string
type MsgError string
type MsgBulkStr string
type MsgInteger int64
type MsgArray []Message
func (m MsgInteger) String() string {
return fmt.Sprintf("%d", m)
}
func (m MsgBulkStr) String() string {
return string(m)
}
func (m MsgError) String() string {
return string(m)
}
func (m MsgSimpleStr) String() string {
return string(m)
}
// func (m MsgArray) String() string {
// return fmt.Sprintf("[%s]", strings.Join(Map(m, Message.String), ","))
// }
func (m RedisMessage) String() string {
return m.Choice.String()
}
type RedisMessage struct {
RedisType RespT
Raw string
Choice Message
}
func NewParseError(got RespT, expected ...RespT) error {
return fmt.Errorf("got %v but expected %v", got, expected)
}
func (msg RedisMessage) Symbol() rune {
return rune(msg.RedisType)
}
func (msg RedisMessage) AsString() (string, error) {
if conv, ok := msg.Choice.(MsgSimpleStr); ok {
return string(conv), nil
} else if conv, ok := msg.Choice.(MsgBulkStr); ok {
return string(conv), nil
} else {
return "", NewParseError(msg.RedisType, SimpleString, BulkString)
}
}
func (msg RedisMessage) AsInteger() (int64, error) {
if conv, ok := msg.Choice.(MsgInteger); ok {
return int64(conv), nil
} else {
return 0, NewParseError(msg.RedisType, Integer)
}
}
func (msg RedisMessage) AsError() (error, error) {
if conv, ok := msg.Choice.(MsgError); ok {
return errors.New(string(conv)), nil
} else {
return nil, NewParseError(msg.RedisType, Error)
}
}
// func (msg RedisMessage) AsArray() ([]Message, error) {
// if conv, ok := (msg.Choice).(MsgArray); ok {
// return conv, nil
// } else {
// return nil, NewParseError(msg.RedisType, Array)
// }
// }
type RedisReader struct {
Rd *textproto.Reader
Out chan RedisMessage
}
// Reset the parsing state of the reader
func (rr *RedisReader) Reset() {
}
func NewRespReader(tp *textproto.Reader, out chan RedisMessage) RedisReader {
return RedisReader{
Rd: tp,
Out: out,
}
}
// Fetch grabs the next incoming RESP type from the IO input
func Fetch(rr *RedisReader) *RedisMessage {
byteCode, err := rr.TryReadByte()
if err != nil {
return nil
}
switch RespT(byteCode) {
case BulkString:
return RespBulkStr(rr)
case SimpleString:
return RespSimpleStr(rr)
case Integer:
return RespInt(rr)
case Error:
return RespError(rr)
// case Array:
// return RespArray(rr)
default:
panic(fmt.Sprint("Unknown bytecode: ", byteCode))
}
}
// Scan is the main API to send redis messages out to a channel.
func (rr *RedisReader) Scan() {
rr.Out <- *Fetch(rr)
}
// RespSimpleStr attempts to parse a string from the resulting line.
//
// TODO: I don't think we need the high level facilities from net/textproto anymore
// since the parsing is quite trivial.
func RespSimpleStr(rr *RedisReader) *RedisMessage {
s, err := rr.Rd.ReadLine()
if err != nil {
log.Printf("Fatal RespSimpleStr: %+v", err)
return nil
}
return &RedisMessage{
RedisType: RespT(SimpleString),
Raw: s,
Choice: MsgSimpleStr(s),
}
}
func RespError(rr *RedisReader) *RedisMessage {
s, err := rr.Rd.ReadLine()
if err != nil {
log.Printf("Fatal RespError: %+v", err)
return nil
}
return &RedisMessage{
RedisType: RespT(Error),
Raw: s,
Choice: MsgError(s),
}
}
// RespSimpleStr attempts to parse a string from the resulting line.
//
// It first expects a length value parsed as an integer,
// then directly copies the subsequent payload into a string
// buffer
func RespBulkStr(rr *RedisReader) *RedisMessage {
len, err := loopReadInt(rr)
if err != nil {
return nil
}
// Read the string data into buffer
bulkStr := make([]byte, len)
io.ReadFull(rr.Rd.R, bulkStr)
// Discard two bytes since we don't need the '\r\n'
_, err = rr.Rd.R.Discard(2)
if err != nil {
log.Printf("Fatal: %+v", err)
}
return &RedisMessage{
RedisType: BulkString,
Raw: string(bulkStr),
Choice: MsgBulkStr(string(bulkStr)),
}
}
// RespInt reads an integer from the IO stream
func RespInt(rr *RedisReader) *RedisMessage {
val, err := loopReadInt(rr)
if err != nil {
return nil
}
return &RedisMessage{
RedisType: Integer,
Raw: fmt.Sprintf("%d", val),
Choice: MsgInteger(val),
}
}
// RespArray reads an array of RESP objects from the IO stream
// func RespArray(rr *RedisReader) *RedisMessage {
// len, err := loopReadInt(rr)
// if err != nil {
// return nil
// }
// fetched := make([]RedisMessage, 0, len)
// for i := 0; i < len; i++ {
// fetched[i] = *Fetch(rr)
// }
// return &RedisMessage{
// RedisType: Array,
// Raw: fmt.Sprint(Map(fetched, func(rm RedisMessage) string { return rm.Raw })),
// Choice: MsgArray(Map(fetched, func(rm RedisMessage) Message { return rm.Choice })),
// }
// }
// loopReadInt reads from IO and returns an integer, discarding \r\n.
func loopReadInt(rr *RedisReader) (int, error) {
val := 0
for b, _ := rr.TryReadByte(); b != '\r'; b, _ = rr.TryReadByte() {
val = (val * 10) + int(b-byte('0'))
}
_, err := rr.Rd.R.Discard(1)
if err != nil {
return 0, fmt.Errorf("Fatal loopReadInt: %+v", err)
}
return val, nil
}
// TryReadByte attempts to read a single byte from IO and panics on any error
func (rr *RedisReader) TryReadByte() (byte, error) {
b, err := rr.Rd.R.ReadByte()
if err != nil {
return byte('0'), fmt.Errorf("Fatal TryReadByte: %+v", err)
}
return b, nil
}
// Map. Your standard good old fashioned generic map function :)
//
// A la []T -> []K
func Map[T any, K any](slice []T, fn func(T) K) []K {
out := make([]K, 0, len(slice))
for i := range out {
out[i] = fn(slice[i])
}
return out
}
func loopScan(reader *textproto.Reader, msg_ch chan RedisMessage) {
rr := NewRespReader(reader, msg_ch)
for {
rr.Scan()
}
}
// repl starts an read input loop on the standard input.
//
// Commands are sent out after a new line is parsed.
//
// TODO: Improve ergonomics of the repl.
func repl(input *textproto.Reader, wr *textproto.Writer) {
for {
print("> ")
input_str, err := input.ReadLine()
if len(input_str) > 0 && err == nil {
// The redis server expects arrays of bulk strings for sending commands.
commands := strings.Split(input_str, " ")
// Specify the array length and create the bulk strings
pipe := []string{fmt.Sprintf("*%d", len(commands))}
pipe = append(pipe, createBulkStrings(commands)...)
for _, out := range pipe {
wr.PrintfLine(out)
}
}
}
}
// createBulkStrings converts normal strings into bulk strings to send to redis.
func createBulkStrings(commands []string) []string {
cmds := make([]string, len(commands)*2)
for i, v := range commands {
cmds[i*2] = fmt.Sprintf("$%d", len(commands[i]))
cmds[(i*2)+1] = v
}
return cmds
}
// cmd/client starts a simple Read-Send-Print-Loop (RSPL?)
// with a redis server.
//
// TODO: Implement command flags
func Init(host string, password string) (*textproto.Writer, *textproto.Reader) {
log.Printf("yo")
// Create the connection to the redis server
// TODO: cli, rm hardcode
conn, err := net.Dial("tcp", host)
if err != nil {
log.Printf("[net] unable to connect: %v", err)
return nil, nil
}
defer conn.Close()
// Create the readers and writers we will pass to our subroutines
msg_channel := make(chan RedisMessage)
write_sock := textproto.NewWriter(bufio.NewWriter(conn))
read_sock := textproto.NewReader(bufio.NewReader(conn))
go runAsWaitGroup(
// Subscribe to redis connection
func() {
loopScan(read_sock, msg_channel)
},
// Write to output
func() {
for {
msg := <-msg_channel
log.Printf("%v", msg)
}
},
).Wait()
log.Printf("yolo")
return write_sock, read_sock
}
// runAsWaitGroup runs closures within a sync.WaitGroup
//
// Calling .Wait() on the resultant WaitGroup is a blocking operation until all closures finish running.
func runAsWaitGroup(closures ...func()) *sync.WaitGroup {
wg := sync.WaitGroup{}
for _, fn := range closures {
wg.Add(1)
go func(fn func()) {
fn()
wg.Done()
}(fn)
}
return &wg
}