-
Notifications
You must be signed in to change notification settings - Fork 7
/
resp_parser.go
133 lines (116 loc) · 2.67 KB
/
resp_parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package main
import (
"bytes"
"errors"
"regexp"
"unsafe"
)
const (
TypeString = iota
TypeBulkString
TypeInteger
TypeArray
TypeError
TypeUnknown
)
//RespReader presents RESP packges from ethernet.
type RespReader struct {
payload []byte
command string
args string
size int
}
//NewRespReader create a RESP object on the app
func NewRespReader(payload, sep []byte, cleaner *regexp.Regexp, size int) (*RespReader, error) {
r := &RespReader{
payload: payload,
}
err := r.parse(sep, cleaner, size)
return r, err
}
//parse basic RESP parser for my case.
//I use unsafe pointer for string conversion because I need to lower memory allocation.
func (c *RespReader) parse(sep []byte, cleaner *regexp.Regexp, maxsize int) error {
if c.payload == nil {
return errors.New("payload is nil")
}
if len(c.payload) < 1 {
return errors.New("payload is empty")
}
switch c.Type() {
case TypeArray:
pp := bytes.Split(c.payload, []byte{'\r', '\n'})
argsindex := []int{}
for i := 0; i < len(pp); i++ {
if bytes.HasPrefix(pp[i], []byte{'$'}) {
argsindex = append(argsindex, i+1)
if len(argsindex) > 2 {
break
}
}
}
if len(argsindex) > 0 {
if argsindex[0] < len(pp) {
c.command = *(*string)(unsafe.Pointer(&pp[argsindex[0]]))
}
}
if len(argsindex) >= 2 {
first := pp[argsindex[1]]
if len(first) > maxsize {
if maxsize > 0 {
first = pp[argsindex[1]][0 : maxsize-1]
}
}
if len(sep) > 0 {
first = c.removeLast(first, sep)
}
if cleaner != nil {
first = c.cleanMatched(first, cleaner, sep)
}
c.args = *(*string)(unsafe.Pointer(&first))
}
default:
return errors.New("unsupported type")
}
return nil
}
func (c *RespReader) removeLast(payload []byte, sep []byte) []byte {
explode := bytes.Split(payload, sep)
if len(explode) < 2 {
return payload
}
explode = explode[0 : len(explode)-1]
return bytes.Join(explode, sep)
}
func (c *RespReader) cleanMatched(payload []byte, pattern *regexp.Regexp, trim []byte) []byte {
//TODO: probably ReplaceAll is slow. refactor it.
return bytes.TrimSuffix(pattern.ReplaceAll(payload, []byte{}), trim)
}
//Command RESP command name
func (c *RespReader) Command() string {
return c.command
}
//Args RESP command arguments
func (c *RespReader) Args() string {
return c.args
}
//Size RESP size of the command
func (c *RespReader) Size() float64 {
return float64(len(c.payload))
}
//Type returns the RESP command or response type
func (c *RespReader) Type() int {
switch c.payload[0] {
case '+':
return TypeString
case '$':
return TypeBulkString
case '*':
return TypeArray
case ':':
return TypeInteger
case '-':
return TypeError
}
return TypeUnknown
}