mirror of https://github.com/tidwall/redcon.git
first commit
This commit is contained in:
commit
ffcf13732b
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Josh Baker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,110 @@
|
|||
Redcon [GoDoc](https://godoc.org/github.com/tidwall/redcon?status.svg)](https://godoc.org/github.com/tidwall/redcon)
|
||||
======
|
||||
Super fast Redis RESP server implementation.
|
||||
Supports pipelining and telnet commands.
|
||||
|
||||
There is only one function `ListenAndServe`, and one type `Conn`.
|
||||
|
||||
Here's a full example of a Redis clone that accepts:
|
||||
|
||||
- SET key value
|
||||
- GET key
|
||||
- DEL key
|
||||
- PING
|
||||
- QUIT
|
||||
|
||||
You can run this example from a terminal:
|
||||
|
||||
```sh
|
||||
go run examples/clone.go
|
||||
```
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/tidwall/redcon"
|
||||
)
|
||||
|
||||
var addr = ":6380"
|
||||
|
||||
func main() {
|
||||
var mu sync.RWMutex
|
||||
var items = make(map[string]string)
|
||||
go log.Printf("started server at %s", addr)
|
||||
err := redcon.ListenAndServe(addr,
|
||||
func(conn redcon.Conn, commands [][]string) {
|
||||
for _, args := range commands {
|
||||
switch strings.ToLower(args[0]) {
|
||||
default:
|
||||
conn.WriteError("ERR unknown command '" + args[0] + "'")
|
||||
case "ping":
|
||||
conn.WriteString("PONG")
|
||||
case "quit":
|
||||
conn.WriteString("OK")
|
||||
conn.Close()
|
||||
case "set":
|
||||
if len(args) != 3 {
|
||||
conn.WriteError("ERR wrong number of arguments for '" + args[0] + "' command")
|
||||
continue
|
||||
}
|
||||
mu.Lock()
|
||||
items[args[1]] = args[2]
|
||||
mu.Unlock()
|
||||
conn.WriteString("OK")
|
||||
case "get":
|
||||
if len(args) != 2 {
|
||||
conn.WriteError("ERR wrong number of arguments for '" + args[0] + "' command")
|
||||
continue
|
||||
}
|
||||
mu.RLock()
|
||||
val, ok := items[args[1]]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
conn.WriteNull()
|
||||
} else {
|
||||
conn.WriteBulk(val)
|
||||
}
|
||||
case "del":
|
||||
if len(args) != 2 {
|
||||
conn.WriteError("ERR wrong number of arguments for '" + args[0] + "' command")
|
||||
continue
|
||||
}
|
||||
mu.Lock()
|
||||
_, ok := items[args[1]]
|
||||
delete(items, args[1])
|
||||
mu.Unlock()
|
||||
if !ok {
|
||||
conn.WriteInt(0)
|
||||
} else {
|
||||
conn.WriteInt(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
func(conn redcon.Conn) bool {
|
||||
log.Printf("accept: %s", conn.RemoteAddr())
|
||||
return true
|
||||
},
|
||||
func(conn redcon.Conn, err error) {
|
||||
log.Printf("closed: %s, err: %v", conn.RemoteAddr(), err)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Contact
|
||||
-------
|
||||
Josh Baker [@tidwall](http://twitter.com/tidwall)
|
||||
|
||||
License
|
||||
-------
|
||||
Redcon source code is available under the MIT [License](/LICENSE).
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/tidwall/redcon"
|
||||
)
|
||||
|
||||
var addr = ":6380"
|
||||
|
||||
func main() {
|
||||
var mu sync.RWMutex
|
||||
var items = make(map[string]string)
|
||||
go log.Printf("started server at %s", addr)
|
||||
err := redcon.ListenAndServe(addr,
|
||||
func(conn redcon.Conn, commands [][]string) {
|
||||
for _, args := range commands {
|
||||
switch strings.ToLower(args[0]) {
|
||||
default:
|
||||
conn.WriteError("ERR unknown command '" + args[0] + "'")
|
||||
case "ping":
|
||||
conn.WriteString("PONG")
|
||||
case "quit":
|
||||
conn.WriteString("OK")
|
||||
conn.Close()
|
||||
case "set":
|
||||
if len(args) != 3 {
|
||||
conn.WriteError("ERR wrong number of arguments for '" + args[0] + "' command")
|
||||
continue
|
||||
}
|
||||
mu.Lock()
|
||||
items[args[1]] = args[2]
|
||||
mu.Unlock()
|
||||
conn.WriteString("OK")
|
||||
case "get":
|
||||
if len(args) != 2 {
|
||||
conn.WriteError("ERR wrong number of arguments for '" + args[0] + "' command")
|
||||
continue
|
||||
}
|
||||
mu.RLock()
|
||||
val, ok := items[args[1]]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
conn.WriteNull()
|
||||
} else {
|
||||
conn.WriteBulk(val)
|
||||
}
|
||||
case "del":
|
||||
if len(args) != 2 {
|
||||
conn.WriteError("ERR wrong number of arguments for '" + args[0] + "' command")
|
||||
continue
|
||||
}
|
||||
mu.Lock()
|
||||
_, ok := items[args[1]]
|
||||
delete(items, args[1])
|
||||
mu.Unlock()
|
||||
if !ok {
|
||||
conn.WriteInt(0)
|
||||
} else {
|
||||
conn.WriteInt(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
func(conn redcon.Conn) bool {
|
||||
log.Printf("accept: %s", conn.RemoteAddr())
|
||||
return true
|
||||
},
|
||||
func(conn redcon.Conn, err error) {
|
||||
log.Printf("closed: %s, err: %v", conn.RemoteAddr(), err)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,424 @@
|
|||
package redcon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Conn interface {
|
||||
RemoteAddr() string
|
||||
Close() error
|
||||
WriteError(msg string)
|
||||
WriteString(str string)
|
||||
WriteBulk(bulk string)
|
||||
WriteInt(num int)
|
||||
WriteArray(count int)
|
||||
WriteNull()
|
||||
}
|
||||
|
||||
var (
|
||||
errUnbalancedQuotes = &errProtocol{"unbalanced quotes in request"}
|
||||
errInvalidBulkLength = &errProtocol{"invalid bulk length"}
|
||||
errInvalidMultiBulkLength = &errProtocol{"invalid multibulk length"}
|
||||
)
|
||||
|
||||
type errProtocol struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err *errProtocol) Error() string {
|
||||
return "Protocol error: " + err.msg
|
||||
}
|
||||
|
||||
func ListenAndServe(
|
||||
addr string, handler func(conn Conn, cmds [][]string),
|
||||
accept func(conn Conn) bool, closed func(conn Conn, err error),
|
||||
) error {
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ln.Close()
|
||||
var mu sync.Mutex
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wr := newWriter(conn)
|
||||
wrc := &connWriter{wr, conn.RemoteAddr().String()}
|
||||
if accept != nil && !accept(wrc) {
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
go func() {
|
||||
var err error
|
||||
defer func() {
|
||||
conn.Close()
|
||||
if closed != nil {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
closed(wrc, err)
|
||||
}
|
||||
}()
|
||||
rd := newReader(conn)
|
||||
err = func() error {
|
||||
for {
|
||||
cmds, err := rd.ReadCommands()
|
||||
if err != nil {
|
||||
if err, ok := err.(*errProtocol); ok {
|
||||
// All protocol errors should attempt a response to
|
||||
// the client. Ignore errors.
|
||||
wr.WriteError("ERR " + err.Error())
|
||||
wr.Flush()
|
||||
}
|
||||
return err
|
||||
}
|
||||
if len(cmds) > 0 {
|
||||
handler(wrc, cmds)
|
||||
}
|
||||
if wr.err != nil {
|
||||
if wr.err == errClosed {
|
||||
return nil
|
||||
}
|
||||
return wr.err
|
||||
}
|
||||
if err := wr.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
type connWriter struct {
|
||||
wr *respWriter
|
||||
addr string
|
||||
}
|
||||
|
||||
func (wrc *connWriter) Close() error {
|
||||
return wrc.wr.Close()
|
||||
}
|
||||
func (wrc *connWriter) WriteString(str string) {
|
||||
wrc.wr.WriteString(str)
|
||||
}
|
||||
func (wrc *connWriter) WriteBulk(bulk string) {
|
||||
wrc.wr.WriteBulk(bulk)
|
||||
}
|
||||
func (wrc *connWriter) WriteInt(num int) {
|
||||
wrc.wr.WriteInt(num)
|
||||
}
|
||||
func (wrc *connWriter) WriteError(msg string) {
|
||||
wrc.wr.WriteError(msg)
|
||||
}
|
||||
func (wrc *connWriter) WriteArray(count int) {
|
||||
wrc.wr.WriteMultiBulkStart(count)
|
||||
}
|
||||
func (wrc *connWriter) WriteNull() {
|
||||
wrc.wr.WriteNull()
|
||||
}
|
||||
func (wrc *connWriter) RemoteAddr() string {
|
||||
return wrc.addr
|
||||
}
|
||||
|
||||
// Reader represents a RESP command reader.
|
||||
type respReader struct {
|
||||
r io.Reader // base reader
|
||||
b []byte // unprocessed bytes
|
||||
a []byte // static read buffer
|
||||
}
|
||||
|
||||
// NewReader returns a RESP command reader.
|
||||
func newReader(r io.Reader) *respReader {
|
||||
return &respReader{
|
||||
r: r,
|
||||
a: make([]byte, 8192),
|
||||
}
|
||||
}
|
||||
|
||||
// ReadCommands reads one or more commands from the reader.
|
||||
func (r *respReader) ReadCommands() ([][]string, error) {
|
||||
if len(r.b) > 0 {
|
||||
// we have some potential commands.
|
||||
var cmds [][]string
|
||||
next:
|
||||
switch r.b[0] {
|
||||
default:
|
||||
// just a plain text command
|
||||
for i := 0; i < len(r.b); i++ {
|
||||
if r.b[i] == '\n' {
|
||||
var line []byte
|
||||
if i > 0 && r.b[i-1] == '\r' {
|
||||
line = r.b[:i-1]
|
||||
} else {
|
||||
line = r.b[:i]
|
||||
}
|
||||
var args []string
|
||||
var quote bool
|
||||
var escape bool
|
||||
outer:
|
||||
for {
|
||||
nline := make([]byte, 0, len(line))
|
||||
for i := 0; i < len(line); i++ {
|
||||
c := line[i]
|
||||
if !quote {
|
||||
if c == ' ' {
|
||||
if len(nline) > 0 {
|
||||
args = append(args, string(nline))
|
||||
}
|
||||
line = line[i+1:]
|
||||
continue outer
|
||||
}
|
||||
if c == '"' {
|
||||
if i != 0 {
|
||||
return nil, errUnbalancedQuotes
|
||||
}
|
||||
quote = true
|
||||
line = line[i+1:]
|
||||
continue outer
|
||||
}
|
||||
} else {
|
||||
if escape {
|
||||
escape = false
|
||||
switch c {
|
||||
case 'n':
|
||||
c = '\n'
|
||||
case 'r':
|
||||
c = '\r'
|
||||
case 't':
|
||||
c = '\t'
|
||||
}
|
||||
} else if c == '"' {
|
||||
quote = false
|
||||
args = append(args, string(nline))
|
||||
line = line[i+1:]
|
||||
if len(line) > 0 && line[0] != ' ' {
|
||||
return nil, errUnbalancedQuotes
|
||||
}
|
||||
continue outer
|
||||
} else if c == '\\' {
|
||||
escape = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
nline = append(nline, c)
|
||||
}
|
||||
if quote {
|
||||
return nil, errUnbalancedQuotes
|
||||
}
|
||||
if len(line) > 0 {
|
||||
args = append(args, string(line))
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(args) > 0 {
|
||||
cmds = append(cmds, args)
|
||||
}
|
||||
r.b = r.b[i+1:]
|
||||
if len(r.b) > 0 {
|
||||
goto next
|
||||
} else {
|
||||
goto done
|
||||
}
|
||||
}
|
||||
}
|
||||
case '*':
|
||||
// resp formatted command
|
||||
var si int
|
||||
outer2:
|
||||
for i := 0; i < len(r.b); i++ {
|
||||
var args []string
|
||||
if r.b[i] == '\n' {
|
||||
if r.b[i-1] != '\r' {
|
||||
return nil, errInvalidMultiBulkLength
|
||||
}
|
||||
ni, err := strconv.ParseInt(string(r.b[si+1:i-1]), 10, 64)
|
||||
if err != nil || ni <= 0 {
|
||||
return nil, errInvalidMultiBulkLength
|
||||
}
|
||||
args = make([]string, 0, int(ni))
|
||||
for j := 0; j < int(ni); j++ {
|
||||
// read bulk length
|
||||
i++
|
||||
if i < len(r.b) {
|
||||
if r.b[i] != '$' {
|
||||
return nil, &errProtocol{"expected '$', got '" +
|
||||
string(r.b[i]) + "'"}
|
||||
}
|
||||
si = i
|
||||
for ; i < len(r.b); i++ {
|
||||
if r.b[i] == '\n' {
|
||||
if r.b[i-1] != '\r' {
|
||||
return nil, errInvalidBulkLength
|
||||
}
|
||||
s := string(r.b[si+1 : i-1])
|
||||
ni2, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil || ni2 < 0 {
|
||||
return nil, errInvalidBulkLength
|
||||
}
|
||||
if i+int(ni2)+2 >= len(r.b) {
|
||||
// not ready
|
||||
break outer2
|
||||
}
|
||||
if r.b[i+int(ni2)+2] != '\n' ||
|
||||
r.b[i+int(ni2)+1] != '\r' {
|
||||
return nil, errInvalidBulkLength
|
||||
}
|
||||
arg := string(r.b[i+1 : i+1+int(ni2)])
|
||||
i += int(ni2) + 2
|
||||
args = append(args, arg)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(args) == cap(args) {
|
||||
cmds = append(cmds, args)
|
||||
r.b = r.b[i+1:]
|
||||
if len(r.b) > 0 {
|
||||
goto next
|
||||
} else {
|
||||
goto done
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
done:
|
||||
if len(r.b) == 0 {
|
||||
r.b = nil
|
||||
}
|
||||
if len(cmds) > 0 {
|
||||
return cmds, nil
|
||||
}
|
||||
}
|
||||
n, err := r.r.Read(r.a[:])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if len(r.b) > 0 {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
r.b = append(r.b, r.a[:n]...)
|
||||
return r.ReadCommands()
|
||||
}
|
||||
|
||||
var errClosed = errors.New("closed")
|
||||
|
||||
type respWriter struct {
|
||||
w io.Writer
|
||||
b *bytes.Buffer
|
||||
err error
|
||||
}
|
||||
|
||||
func newWriter(w io.Writer) *respWriter {
|
||||
return &respWriter{w: w, b: &bytes.Buffer{}}
|
||||
}
|
||||
|
||||
func (w *respWriter) WriteNull() error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
w.b.WriteString("$-1\r\n")
|
||||
return nil
|
||||
}
|
||||
func (w *respWriter) WriteMultiBulkStart(count int) error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
w.b.WriteByte('*')
|
||||
w.b.WriteString(strconv.FormatInt(int64(count), 10))
|
||||
w.b.WriteString("\r\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *respWriter) WriteBulk(bulk string) error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
w.b.WriteByte('$')
|
||||
w.b.WriteString(strconv.FormatInt(int64(len(bulk)), 10))
|
||||
w.b.WriteString("\r\n")
|
||||
w.b.WriteString(bulk)
|
||||
w.b.WriteString("\r\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *respWriter) Flush() error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
if w.b.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
if _, err := w.b.WriteTo(w.w); err != nil {
|
||||
w.err = err
|
||||
return err
|
||||
}
|
||||
w.b.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *respWriter) WriteMultiBulk(bulks []string) error {
|
||||
if err := w.WriteMultiBulkStart(len(bulks)); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, bulk := range bulks {
|
||||
if err := w.WriteBulk(bulk); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *respWriter) WriteError(msg string) error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
w.b.WriteByte('-')
|
||||
w.b.WriteString(msg)
|
||||
w.b.WriteString("\r\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *respWriter) WriteString(msg string) error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
w.b.WriteByte('+')
|
||||
w.b.WriteString(msg)
|
||||
w.b.WriteString("\r\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *respWriter) WriteInt(num int) error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
w.b.WriteByte(':')
|
||||
w.b.WriteString(strconv.FormatInt(int64(num), 10))
|
||||
w.b.WriteString("\r\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *respWriter) Close() error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
if err := w.Flush(); err != nil {
|
||||
w.err = err
|
||||
return err
|
||||
}
|
||||
w.err = errClosed
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
package redcon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestRandomCommands fills a bunch of random commands and test various
|
||||
// ways that the reader may receive data.
|
||||
func TestRandomCommands(t *testing.T) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// build random commands.
|
||||
gcmds := make([][]string, 10000)
|
||||
for i := 0; i < len(gcmds); i++ {
|
||||
args := make([]string, (rand.Int()%50)+1) // 1-50 args
|
||||
for j := 0; j < len(args); j++ {
|
||||
n := rand.Int() % 10
|
||||
if j == 0 {
|
||||
n++
|
||||
}
|
||||
arg := make([]byte, n)
|
||||
for k := 0; k < len(arg); k++ {
|
||||
arg[k] = byte(rand.Int() % 0xFF)
|
||||
}
|
||||
args[j] = string(arg)
|
||||
}
|
||||
gcmds[i] = args
|
||||
}
|
||||
// create a list of a buffers
|
||||
var bufs []string
|
||||
|
||||
// pipe valid RESP commands
|
||||
for i := 0; i < len(gcmds); i++ {
|
||||
args := gcmds[i]
|
||||
msg := fmt.Sprintf("*%d\r\n", len(args))
|
||||
for j := 0; j < len(args); j++ {
|
||||
msg += fmt.Sprintf("$%d\r\n%s\r\n", len(args[j]), args[j])
|
||||
}
|
||||
bufs = append(bufs, msg)
|
||||
}
|
||||
bufs = append(bufs, "RESET THE INDEX\r\n")
|
||||
|
||||
// pipe valid plain commands
|
||||
for i := 0; i < len(gcmds); i++ {
|
||||
args := gcmds[i]
|
||||
var msg string
|
||||
for j := 0; j < len(args); j++ {
|
||||
quotes := false
|
||||
var narg []byte
|
||||
arg := args[j]
|
||||
if len(arg) == 0 {
|
||||
quotes = true
|
||||
}
|
||||
for k := 0; k < len(arg); k++ {
|
||||
switch arg[k] {
|
||||
default:
|
||||
narg = append(narg, arg[k])
|
||||
case ' ':
|
||||
quotes = true
|
||||
narg = append(narg, arg[k])
|
||||
case '\\', '"', '*':
|
||||
quotes = true
|
||||
narg = append(narg, '\\', arg[k])
|
||||
case '\r':
|
||||
quotes = true
|
||||
narg = append(narg, '\\', 'r')
|
||||
case '\n':
|
||||
quotes = true
|
||||
narg = append(narg, '\\', 'n')
|
||||
}
|
||||
}
|
||||
msg += " "
|
||||
if quotes {
|
||||
msg += "\""
|
||||
}
|
||||
msg += string(narg)
|
||||
if quotes {
|
||||
msg += "\""
|
||||
}
|
||||
}
|
||||
if msg != "" {
|
||||
msg = msg[1:]
|
||||
}
|
||||
msg += "\r\n"
|
||||
bufs = append(bufs, msg)
|
||||
}
|
||||
bufs = append(bufs, "RESET THE INDEX\r\n")
|
||||
|
||||
// pipe valid RESP commands in broken chunks
|
||||
lmsg := ""
|
||||
for i := 0; i < len(gcmds); i++ {
|
||||
args := gcmds[i]
|
||||
msg := fmt.Sprintf("*%d\r\n", len(args))
|
||||
for j := 0; j < len(args); j++ {
|
||||
msg += fmt.Sprintf("$%d\r\n%s\r\n", len(args[j]), args[j])
|
||||
}
|
||||
msg = lmsg + msg
|
||||
if len(msg) > 0 {
|
||||
lmsg = msg[len(msg)/2:]
|
||||
msg = msg[:len(msg)/2]
|
||||
}
|
||||
bufs = append(bufs, msg)
|
||||
}
|
||||
bufs = append(bufs, lmsg)
|
||||
bufs = append(bufs, "RESET THE INDEX\r\n")
|
||||
|
||||
// pipe valid RESP commands in large broken chunks
|
||||
lmsg = ""
|
||||
for i := 0; i < len(gcmds); i++ {
|
||||
args := gcmds[i]
|
||||
msg := fmt.Sprintf("*%d\r\n", len(args))
|
||||
for j := 0; j < len(args); j++ {
|
||||
msg += fmt.Sprintf("$%d\r\n%s\r\n", len(args[j]), args[j])
|
||||
}
|
||||
if len(lmsg) < 1500 {
|
||||
lmsg += msg
|
||||
continue
|
||||
}
|
||||
msg = lmsg + msg
|
||||
if len(msg) > 0 {
|
||||
lmsg = msg[len(msg)/2:]
|
||||
msg = msg[:len(msg)/2]
|
||||
}
|
||||
bufs = append(bufs, msg)
|
||||
}
|
||||
bufs = append(bufs, lmsg)
|
||||
bufs = append(bufs, "RESET THE INDEX\r\n")
|
||||
|
||||
// Pipe the buffers in a background routine
|
||||
rd, wr := io.Pipe()
|
||||
go func() {
|
||||
defer wr.Close()
|
||||
for _, msg := range bufs {
|
||||
io.WriteString(wr, msg)
|
||||
}
|
||||
}()
|
||||
defer rd.Close()
|
||||
cnt := 0
|
||||
idx := 0
|
||||
start := time.Now()
|
||||
r := newReader(rd)
|
||||
for {
|
||||
cmds, err := r.ReadCommands()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, cmd := range cmds {
|
||||
if len(cmd) == 3 && cmd[0] == "RESET" && cmd[1] == "THE" && cmd[2] == "INDEX" {
|
||||
if idx != len(gcmds) {
|
||||
t.Fatalf("did not process all commands")
|
||||
}
|
||||
idx = 0
|
||||
break
|
||||
}
|
||||
if len(cmd) != len(gcmds[idx]) {
|
||||
t.Fatalf("len not equal for index %d -- %d != %d", idx, len(cmd), len(gcmds[idx]))
|
||||
}
|
||||
for i := 0; i < len(cmd); i++ {
|
||||
if cmd[i] != gcmds[idx][i] {
|
||||
t.Fatalf("not equal for index %d/%d", idx, i)
|
||||
}
|
||||
}
|
||||
idx++
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
if false {
|
||||
dur := time.Now().Sub(start)
|
||||
fmt.Printf("%d commands in %s - %.0f ops/sec\n", cnt, dur, float64(cnt)/(float64(dur)/float64(time.Second)))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestServer(t *testing.T) {
|
||||
err := ListenAndServe(":11111",
|
||||
func(conn Conn, cmds [][]string) {
|
||||
for _, cmd := range cmds {
|
||||
switch strings.ToLower(cmd[0]) {
|
||||
default:
|
||||
conn.WriteError("ERR unknown command '" + cmd[0] + "'")
|
||||
case "ping":
|
||||
conn.WriteString("PONG")
|
||||
case "quit":
|
||||
conn.WriteString("OK")
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
},
|
||||
func(conn Conn) bool {
|
||||
log.Printf("accept: %s", conn.RemoteAddr())
|
||||
return true
|
||||
},
|
||||
func(conn Conn, err error) {
|
||||
log.Printf("closed: %s [%v]", conn.RemoteAddr(), err)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
*/
|
Loading…
Reference in New Issue