Browse Source

feat: 回复功能

runningwater 7 months ago
parent
commit
bda7c94e92
6 changed files with 296 additions and 5 deletions
  1. 17 0
      interface/resp/conn.go
  2. 11 0
      interface/resp/reply.go
  3. 72 0
      resp/reply/consts.go
  4. 75 0
      resp/reply/error.go
  5. 116 0
      resp/reply/reply.go
  6. 5 5
      tcp/echo.go

+ 17 - 0
interface/resp/conn.go

@@ -0,0 +1,17 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/5/26 13:48
+// Desc:
+
+package resp
+
+// Connection 定义客户端连接接口,用于管理连接状态和数据库选择
+type Connection interface {
+	// Write 向客户端写入响应数据
+	Write([]byte) error
+
+	// GetDBIndex 获取当前选择的数据库索引
+	GetDBIndex() int
+
+	// SelectDB 切换当前数据库
+	SelectDB(int)
+}

+ 11 - 0
interface/resp/reply.go

@@ -0,0 +1,11 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/5/26 13:49
+// Desc:
+
+package resp
+
+// Reply 定义响应接口,统一不同类型的响应对象
+type Reply interface {
+	// ToBytes 将响应对象转换为符合Redis协议的字节数组
+	ToBytes() []byte
+}

+ 72 - 0
resp/reply/consts.go

@@ -0,0 +1,72 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/5/26 13:51
+// Desc:
+
+package reply
+
+// PongReply 表示PONG响应,用于Redis协议的PONG响应
+type PongReply struct{}
+
+// ToBytes 将PONG响应转换为字节数组
+// 返回格式:"+PONG\r\n"
+func (p PongReply) ToBytes() []byte {
+	return []byte("+PONG\r\n")
+}
+
+// NewPongReply 创建新的PongReply实例
+func NewPongReply() *PongReply {
+	return &PongReply{}
+}
+
+// OkReply 表示OK响应,用于Redis协议的OK响应
+// 返回格式:"+OK\r\n"
+type OkReply struct{}
+
+// ToBytes 将OK响应转换为字节数组
+// 返回格式:"+OK\r\n"
+func (o OkReply) ToBytes() []byte {
+	return []byte("+OK\r\n")
+}
+
+// NewOkReply 创建新的OkReply实例
+func NewOkReply() *OkReply {
+	return &OkReply{}
+}
+
+// NoReply 表示NO响应,用于Redis协议的NO响应
+type NoReply struct{}
+
+// ToBytes 将NO响应转换为字节数组
+func (n NoReply) ToBytes() []byte {
+	return []byte("")
+}
+
+// NewNoReply 创建新的NoReply实例
+func NewNoReply() *NoReply {
+	return &NoReply{}
+}
+
+// NullBulkReply 表示NO响应,用于Redis协议的NO响应
+// 返回格式:"$-1\r\n"
+type NullBulkReply struct{}
+
+// ToBytes 将NO响应转换为字节数组
+// 返回格式:"$-1\r\n"
+func (n NullBulkReply) ToBytes() []byte {
+	return []byte("$-1\r\n")
+}
+func NewNullBulkReply() *NullBulkReply {
+	return &NullBulkReply{}
+}
+
+// EmptyMultiBulkReply 表示空的多批量回复,用于Redis协议的空的多批量回复(空数组)
+// 返回格式:"*0\r\n"
+type EmptyMultiBulkReply struct{}
+
+// ToBytes 将空的多批量回复转换为字节数组
+func (e EmptyMultiBulkReply) ToBytes() []byte {
+	return []byte("*0\r\n")
+}
+func NewEmptyMultiBulkReply() *EmptyMultiBulkReply {
+	return &EmptyMultiBulkReply{}
+}

+ 75 - 0
resp/reply/error.go

@@ -0,0 +1,75 @@
+package reply
+
+type UnknownErrReply struct{}
+
+func (u *UnknownErrReply) Error() string {
+	return "Err unknown"
+}
+
+func (u *UnknownErrReply) ToBytes() []byte {
+	return []byte("-Err unknown\r\n")
+}
+
+type ArgNumErrReply struct {
+	Cmd string
+}
+
+func (a *ArgNumErrReply) Error() string {
+	return "ERR wrong number of arguments for '" + a.Cmd + "' command"
+}
+
+func (a *ArgNumErrReply) ToBytes() []byte {
+	return []byte("-ERR wrong number of arguments for '" + a.Cmd + "' command\r\n")
+}
+
+func NewArgNumErrReply(cmd string) *ArgNumErrReply {
+	return &ArgNumErrReply{
+		Cmd: cmd,
+	}
+}
+
+type SyntaxErrReply struct{}
+
+func (s *SyntaxErrReply) Error() string {
+	return "Err syntax error"
+}
+
+func (s *SyntaxErrReply) ToBytes() []byte {
+	return []byte("-Err syntax error\r\n")
+}
+
+func NewSyntaxErrReply() *SyntaxErrReply {
+	return &SyntaxErrReply{}
+}
+
+type WrongTypeErrReply struct{}
+
+func (w *WrongTypeErrReply) Error() string {
+	return "WRONGTYPE Operation against a key holding the wrong kind of value"
+}
+
+func (w *WrongTypeErrReply) ToBytes() []byte {
+	return []byte("-WRONGTYPE Operation against a key holding the wrong kind of value\r\n")
+}
+
+func NewWrongTypeErrReply() *WrongTypeErrReply {
+	return &WrongTypeErrReply{}
+}
+
+type ProtocolErrReply struct {
+	Msg string
+}
+
+func (p *ProtocolErrReply) Error() string {
+	return "ERR Protocol error: '" + p.Msg + "'"
+}
+
+func (p *ProtocolErrReply) ToBytes() []byte {
+	return []byte("-ERR Protocol error: '" + p.Msg + "'\r\n")
+}
+
+func NewProtocolErrReply(msg string) *ProtocolErrReply {
+	return &ProtocolErrReply{
+		Msg: msg,
+	}
+}

+ 116 - 0
resp/reply/reply.go

@@ -0,0 +1,116 @@
+// Package reply 实现了Redis序列化协议(RESP)的回复处理功能。
+//
+// 本包包含构建符合Redis RESP协议的各种响应格式的实现,支持以下类型:
+//   - 简单字符串(Simple Strings):  StatusReply
+//   - 错误类型(Errors):            StandardErrReply 及其子类型
+//   - 整型(Integers):             IntReply
+//   - 批量字符串(Bulk Strings):    BulkReply
+//   - 数组(Arrays):               MultiBulkReply
+//
+// 所有回复类型都实现了 `resp.Reply` 接口,通过 `ToBytes()` 方法生成符合RESP协议格式的字节数据。
+//
+// 示例用法:
+//
+//	// 创建整数回复
+//	reply := NewIntReply(42)
+//	output := reply.ToBytes() // 输出 ":42\r\n"
+//
+//	// 创建批量字符串回复
+//	bulk := NewBulkReply([]byte("hello"))
+//	output := bulk.ToBytes() // 输出 "$5\r\nhello\r\n"
+package reply
+
+import (
+	"bytes"
+	"strconv"
+
+	"github.com/runningwater/go-redis/interface/resp"
+)
+
+var (
+	nullBulkReplyBytes = []byte("$-1")
+	CRLF               = "\r\n"
+)
+
+type BulkReply struct {
+	Arg []byte // "test" => "$4\r\ntest\r\n"
+}
+
+func (b *BulkReply) ToBytes() []byte {
+	if len(b.Arg) == 0 {
+		return nullBulkReplyBytes
+	}
+	return []byte("$" + strconv.Itoa(len(b.Arg)) + CRLF + string(b.Arg) + CRLF)
+}
+
+func NewBulkReply(arg []byte) *BulkReply {
+	return &BulkReply{Arg: arg}
+}
+
+type MultiBulkReply struct {
+	Args [][]byte
+}
+
+func (m *MultiBulkReply) ToBytes() []byte {
+	argLen := len(m.Args)
+	var buf bytes.Buffer
+	buf.WriteString("*" + strconv.Itoa(argLen) + CRLF)
+	for _, arg := range m.Args {
+		if arg == nil {
+			buf.WriteString(string(nullBulkReplyBytes) + CRLF)
+		} else {
+			buf.WriteString("$" + strconv.Itoa(len(arg)) + CRLF + string(arg) + CRLF)
+		}
+	}
+
+	return buf.Bytes()
+}
+
+func NewMultiBulkReply(args [][]byte) *MultiBulkReply {
+	return &MultiBulkReply{Args: args}
+}
+
+type StatusReply struct {
+	Status string
+}
+
+func (s *StatusReply) ToBytes() []byte {
+	return []byte("+" + s.Status + CRLF)
+}
+
+func NewStatusReply(status string) *StatusReply {
+	return &StatusReply{Status: status}
+}
+
+type IntReply struct {
+	Code int64
+}
+
+func (i *IntReply) ToBytes() []byte {
+	return []byte(":" + strconv.FormatInt(i.Code, 10) + CRLF)
+}
+
+type ErrorReply interface {
+	Error() string
+	ToBytes() []byte
+}
+
+type StandardErrReply struct {
+	Status string
+}
+
+func (s *StandardErrReply) Error() string {
+	return s.Status
+}
+
+func (s *StandardErrReply) ToBytes() []byte {
+	return []byte("-" + s.Status + CRLF)
+}
+
+func NewStandardErrReply(status string) *StandardErrReply {
+	return &StandardErrReply{Status: status}
+}
+
+func IsErrReply(reply resp.Reply) bool {
+	return reply.ToBytes()[0] == '-'
+}

+ 5 - 5
tcp/echo.go

@@ -29,19 +29,19 @@ func (e *EchoClient) Close() error {
 	return nil
 }
 
-type Echo struct {
+type EchoHandler struct {
 	activeConn sync.Map
 	closing    atomic.Boolean
 }
 
-func NewEcho() *Echo {
-	return &Echo{
+func NewEcho() *EchoHandler {
+	return &EchoHandler{
 		activeConn: sync.Map{},
 		closing:    0,
 	}
 }
 
-func (e *Echo) Handle(ctx context.Context, conn net.Conn) {
+func (e *EchoHandler) Handle(_ctx context.Context, conn net.Conn) {
 	if e.closing.Get() {
 		_ = conn.Close()
 	}
@@ -71,7 +71,7 @@ func (e *Echo) Handle(ctx context.Context, conn net.Conn) {
 	}
 }
 
-func (e *Echo) Close() error {
+func (e *EchoHandler) Close() error {
 	logger.Info("handler shutting down")
 	e.closing.Set(true)