redcon/redcon.go

683 lines
15 KiB
Go
Raw Normal View History

2016-08-20 20:42:34 +03:00
// Package redcon provides a custom redis server implementation.
2016-07-28 17:54:02 +03:00
package redcon
import (
"errors"
"io"
"net"
"strconv"
"sync"
)
2016-08-20 20:42:34 +03:00
// Conn represents a client connection
2016-07-28 17:54:02 +03:00
type Conn interface {
2016-08-20 21:32:41 +03:00
// RemoteAddr returns the remote address of the client connection.
2016-07-28 17:54:02 +03:00
RemoteAddr() string
2016-08-20 21:32:41 +03:00
// Close closes the connection.
2016-07-28 17:54:02 +03:00
Close() error
2016-08-20 21:32:41 +03:00
// WriteError writes an error to the client.
2016-07-28 17:54:02 +03:00
WriteError(msg string)
2016-08-20 21:32:41 +03:00
// WriteString writes a string to the client.
2016-07-28 17:54:02 +03:00
WriteString(str string)
2016-08-20 21:32:41 +03:00
// WriteBulk writes a bulk string to the client.
2016-07-28 17:54:02 +03:00
WriteBulk(bulk string)
2016-08-23 15:54:17 +03:00
// WriteBulkBytes writes bulk bytes to the client.
WriteBulkBytes(bulk []byte)
2016-08-20 21:32:41 +03:00
// WriteInt writes an integer to the client.
2016-07-28 17:54:02 +03:00
WriteInt(num int)
2016-08-20 21:32:41 +03:00
// WriteArray writes an array header. You must then write addtional
// sub-responses to the client to complete the response.
// For example to write two strings:
//
// c.WriteArray(2)
// c.WriteBulk("item 1")
// c.WriteBulk("item 2")
2016-07-28 17:54:02 +03:00
WriteArray(count int)
2016-08-20 21:32:41 +03:00
// WriteNull writes a null to the client
2016-07-28 17:54:02 +03:00
WriteNull()
2016-08-20 21:32:41 +03:00
// SetReadBuffer updates the buffer read size for the connection
2016-08-20 20:42:34 +03:00
SetReadBuffer(bytes int)
2016-08-29 17:01:30 +03:00
// Context returns a user-defined context
Context() interface{}
// SetContext sets a user-defined context
SetContext(v interface{})
2016-07-28 17:54:02 +03:00
}
var (
errUnbalancedQuotes = &errProtocol{"unbalanced quotes in request"}
errInvalidBulkLength = &errProtocol{"invalid bulk length"}
errInvalidMultiBulkLength = &errProtocol{"invalid multibulk length"}
)
2016-09-07 08:56:44 +03:00
const (
2016-09-07 16:04:39 +03:00
defaultBufLen = 4 * 1024
defaultPoolSize = 64
2016-09-07 08:56:44 +03:00
)
2016-08-20 20:42:34 +03:00
2016-07-28 17:54:02 +03:00
type errProtocol struct {
msg string
}
func (err *errProtocol) Error() string {
return "Protocol error: " + err.msg
}
2016-08-21 19:34:23 +03:00
// Server represents a Redcon server.
type Server struct {
2016-09-07 15:50:27 +03:00
mu sync.Mutex
addr string
shandler func(conn Conn, cmds [][]string)
bhandler func(conn Conn, cmds [][][]byte)
accept func(conn Conn) bool
closed func(conn Conn, err error)
ln *net.TCPListener
done bool
conns map[*conn]bool
rdpool [][]byte
wrpool [][]byte
2016-09-07 16:04:39 +03:00
initbuf []byte
2016-08-21 19:34:23 +03:00
}
// NewServer returns a new server
func NewServer(
2016-07-28 17:54:02 +03:00
addr string, handler func(conn Conn, cmds [][]string),
accept func(conn Conn) bool, closed func(conn Conn, err error),
2016-08-21 19:34:23 +03:00
) *Server {
return &Server{
2016-09-07 15:50:27 +03:00
addr: addr,
shandler: handler,
accept: accept,
closed: closed,
conns: make(map[*conn]bool),
2016-09-07 16:04:39 +03:00
initbuf: make([]byte, defaultPoolSize*defaultBufLen),
2016-09-07 15:50:27 +03:00
}
}
// NewServerBytes returns a new server
// It uses []byte instead of string for the handler commands.
func NewServerBytes(
addr string, handler func(conn Conn, cmds [][][]byte),
accept func(conn Conn) bool, closed func(conn Conn, err error),
) *Server {
2016-09-07 16:04:39 +03:00
s := NewServer(addr, nil, accept, closed)
s.bhandler = handler
return s
2016-08-21 19:34:23 +03:00
}
// Close stops listening on the TCP address.
// Already Accepted connections will be closed.
func (s *Server) Close() error {
2016-08-22 21:05:11 +03:00
s.mu.Lock()
defer s.mu.Unlock()
2016-08-21 19:34:23 +03:00
if s.ln == nil {
return errors.New("not serving")
}
s.done = true
return s.ln.Close()
}
// ListenAndServe serves incoming connections.
func (s *Server) ListenAndServe() error {
2016-08-22 20:53:04 +03:00
return s.ListenServeAndSignal(nil)
}
// ListenServeAndSignal serves incoming connections and passes nil or error
// when listening. signal can be nil.
func (s *Server) ListenServeAndSignal(signal chan error) error {
2016-08-21 19:34:23 +03:00
var addr = s.addr
2016-09-07 15:50:27 +03:00
var shandler = s.shandler
var bhandler = s.bhandler
2016-08-21 19:34:23 +03:00
var accept = s.accept
var closed = s.closed
2016-07-28 17:54:02 +03:00
ln, err := net.Listen("tcp", addr)
if err != nil {
2016-08-22 20:53:04 +03:00
if signal != nil {
signal <- err
}
2016-07-28 17:54:02 +03:00
return err
}
2016-08-22 20:53:04 +03:00
if signal != nil {
signal <- nil
}
2016-08-22 21:11:30 +03:00
tln := ln.(*net.TCPListener)
2016-08-22 21:07:42 +03:00
s.mu.Lock()
2016-08-22 21:11:30 +03:00
s.ln = tln
2016-08-22 21:07:42 +03:00
s.mu.Unlock()
2016-08-21 19:34:23 +03:00
defer func() {
ln.Close()
func() {
s.mu.Lock()
defer s.mu.Unlock()
for c := range s.conns {
c.Close()
}
s.conns = nil
}()
}()
2016-07-28 17:54:02 +03:00
for {
2016-08-22 21:11:30 +03:00
tcpc, err := tln.AcceptTCP()
2016-07-28 17:54:02 +03:00
if err != nil {
2016-08-21 19:34:23 +03:00
s.mu.Lock()
done := s.done
s.mu.Unlock()
if done {
return nil
}
2016-07-28 17:54:02 +03:00
return err
}
2016-08-20 19:23:39 +03:00
c := &conn{
tcpc,
newWriter(tcpc),
newReader(tcpc),
tcpc.RemoteAddr().String(),
2016-08-29 17:01:30 +03:00
nil,
2016-08-20 19:23:39 +03:00
}
2016-09-07 08:56:44 +03:00
s.mu.Lock()
if len(s.rdpool) > 0 {
c.rd.buf = s.rdpool[len(s.rdpool)-1]
s.rdpool = s.rdpool[:len(s.rdpool)-1]
}
if len(s.wrpool) > 0 {
c.wr.b = s.wrpool[len(s.wrpool)-1]
s.wrpool = s.wrpool[:len(s.wrpool)-1]
2016-09-07 16:04:39 +03:00
c.wr.b = c.wr.b[:0]
2016-09-07 08:56:44 +03:00
}
s.conns[c] = true
s.mu.Unlock()
2016-08-20 18:19:08 +03:00
if accept != nil && !accept(c) {
2016-09-07 08:56:44 +03:00
s.mu.Lock()
delete(s.conns, c)
s.mu.Unlock()
2016-08-20 18:19:08 +03:00
c.Close()
2016-07-28 17:54:02 +03:00
continue
}
2016-09-07 15:50:27 +03:00
if shandler == nil && bhandler == nil {
shandler = func(conn Conn, cmds [][]string) {}
} else if shandler != nil {
bhandler = nil
} else if bhandler != nil {
shandler = nil
}
go handle(s, c, shandler, bhandler, closed)
2016-08-20 18:19:08 +03:00
}
}
2016-08-21 19:34:23 +03:00
// ListenAndServe creates a new server and binds to addr.
func ListenAndServe(
addr string, handler func(conn Conn, cmds [][]string),
accept func(conn Conn) bool, closed func(conn Conn, err error),
) error {
return NewServer(addr, handler, accept, closed).ListenAndServe()
}
2016-09-07 15:50:27 +03:00
// ListenAndServeBytes creates a new server and binds to addr.
// It uses []byte instead of string for the handler commands.
func ListenAndServeBytes(
addr string, handler func(conn Conn, cmds [][][]byte),
accept func(conn Conn) bool, closed func(conn Conn, err error),
) error {
return NewServerBytes(addr, handler, accept, closed).ListenAndServe()
}
2016-08-21 19:34:23 +03:00
func handle(
s *Server, c *conn,
2016-09-07 15:50:27 +03:00
shandler func(conn Conn, cmds [][]string),
bhandler func(conn Conn, cmds [][][]byte),
2016-08-20 18:19:08 +03:00
closed func(conn Conn, err error)) {
var err error
defer func() {
c.conn.Close()
2016-08-21 19:34:23 +03:00
func() {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.conns, c)
if closed != nil {
if err == io.EOF {
err = nil
}
closed(c, err)
2016-08-20 18:19:08 +03:00
}
2016-09-07 16:04:39 +03:00
if len(s.rdpool) < defaultPoolSize && len(c.rd.buf) < defaultBufLen {
2016-09-07 08:56:44 +03:00
s.rdpool = append(s.rdpool, c.rd.buf)
}
2016-09-07 16:04:39 +03:00
if len(s.wrpool) < defaultPoolSize && len(c.wr.b) < defaultBufLen {
2016-09-07 08:56:44 +03:00
s.wrpool = append(s.wrpool, c.wr.b)
}
2016-08-21 19:34:23 +03:00
}()
2016-08-20 18:19:08 +03:00
}()
err = func() error {
for {
2016-08-20 19:23:39 +03:00
cmds, err := c.rd.ReadCommands()
2016-08-20 18:19:08 +03:00
if err != nil {
if err, ok := err.(*errProtocol); ok {
// All protocol errors should attempt a response to
// the client. Ignore errors.
c.wr.WriteError("ERR " + err.Error())
c.wr.Flush()
2016-07-28 17:54:02 +03:00
}
2016-08-20 18:19:08 +03:00
return err
}
if len(cmds) > 0 {
2016-09-07 15:50:27 +03:00
if shandler != nil {
// convert bytes to strings
scmds := make([][]string, len(cmds))
for i := 0; i < len(cmds); i++ {
scmds[i] = make([]string, len(cmds[i]))
for j := 0; j < len(scmds[i]); j++ {
scmds[i][j] = string(scmds[i][j])
}
}
shandler(c, scmds)
} else if bhandler != nil {
// copy the byte commands once, before exposing to the
// client.
for i := 0; i < len(cmds); i++ {
for j := 0; j < len(cmds[i]); j++ {
nb := make([]byte, len(cmds[i][j]))
copy(nb, cmds[i][j])
cmds[i][j] = nb
}
}
bhandler(c, cmds)
}
2016-08-20 18:19:08 +03:00
}
if c.wr.err != nil {
if c.wr.err == errClosed {
return nil
2016-07-28 17:54:02 +03:00
}
2016-08-20 18:19:08 +03:00
return c.wr.err
}
if err := c.wr.Flush(); err != nil {
return err
}
}
}()
2016-07-28 17:54:02 +03:00
}
2016-08-20 18:19:08 +03:00
type conn struct {
conn *net.TCPConn
wr *writer
2016-08-20 19:23:39 +03:00
rd *reader
2016-07-28 17:54:02 +03:00
addr string
2016-08-29 17:01:30 +03:00
ctx interface{}
2016-07-28 17:54:02 +03:00
}
2016-08-20 18:19:08 +03:00
func (c *conn) Close() error {
2016-08-21 19:34:23 +03:00
err := c.wr.Close() // flush and close the writer
c.conn.Close() // close the connection. ignore this error
return err // return the writer error only
2016-07-28 17:54:02 +03:00
}
2016-08-29 17:01:30 +03:00
func (c *conn) Context() interface{} {
return c.ctx
}
func (c *conn) SetContext(v interface{}) {
c.ctx = v
}
2016-08-20 18:19:08 +03:00
func (c *conn) WriteString(str string) {
c.wr.WriteString(str)
2016-07-28 17:54:02 +03:00
}
2016-08-20 18:19:08 +03:00
func (c *conn) WriteBulk(bulk string) {
c.wr.WriteBulk(bulk)
2016-07-28 17:54:02 +03:00
}
2016-08-23 15:54:17 +03:00
func (c *conn) WriteBulkBytes(bulk []byte) {
c.wr.WriteBulkBytes(bulk)
}
2016-08-20 18:19:08 +03:00
func (c *conn) WriteInt(num int) {
c.wr.WriteInt(num)
2016-07-28 17:54:02 +03:00
}
2016-08-20 18:19:08 +03:00
func (c *conn) WriteError(msg string) {
c.wr.WriteError(msg)
2016-07-28 17:54:02 +03:00
}
2016-08-20 18:19:08 +03:00
func (c *conn) WriteArray(count int) {
2016-08-21 19:34:23 +03:00
c.wr.WriteArrayStart(count)
2016-07-28 17:54:02 +03:00
}
2016-08-20 18:19:08 +03:00
func (c *conn) WriteNull() {
c.wr.WriteNull()
2016-07-28 17:54:02 +03:00
}
2016-08-20 18:19:08 +03:00
func (c *conn) RemoteAddr() string {
return c.addr
2016-07-28 17:54:02 +03:00
}
2016-08-20 20:42:34 +03:00
func (c *conn) SetReadBuffer(bytes int) {
c.rd.buflen = bytes
}
2016-07-28 17:54:02 +03:00
// Reader represents a RESP command reader.
2016-08-20 19:23:39 +03:00
type reader struct {
2016-08-20 20:42:34 +03:00
r io.Reader // base reader
2016-09-07 08:56:44 +03:00
buf []byte
start int
end int
buflen int
2016-07-28 17:54:02 +03:00
}
// NewReader returns a RESP command reader.
2016-08-20 19:57:29 +03:00
func newReader(r io.Reader) *reader {
2016-08-20 19:23:39 +03:00
return &reader{
2016-08-20 20:42:34 +03:00
r: r,
buflen: defaultBufLen,
2016-07-28 17:54:02 +03:00
}
}
// ReadCommands reads one or more commands from the reader.
2016-09-07 15:50:27 +03:00
func (r *reader) ReadCommands() ([][][]byte, error) {
2016-09-07 08:56:44 +03:00
if r.end-r.start > 0 {
b := r.buf[r.start:r.end]
2016-07-28 17:54:02 +03:00
// we have some potential commands.
2016-09-07 15:50:27 +03:00
var cmds [][][]byte
2016-07-28 17:54:02 +03:00
next:
2016-09-07 08:56:44 +03:00
switch b[0] {
2016-07-28 17:54:02 +03:00
default:
// just a plain text command
2016-09-07 08:56:44 +03:00
for i := 0; i < len(b); i++ {
if b[i] == '\n' {
2016-07-28 17:54:02 +03:00
var line []byte
2016-09-07 08:56:44 +03:00
if i > 0 && b[i-1] == '\r' {
line = b[:i-1]
2016-07-28 17:54:02 +03:00
} else {
2016-09-07 08:56:44 +03:00
line = b[:i]
2016-07-28 17:54:02 +03:00
}
2016-09-07 15:50:27 +03:00
var args [][]byte
2016-07-28 17:54:02 +03:00
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 {
2016-09-07 15:50:27 +03:00
args = append(args, nline)
2016-07-28 17:54:02 +03:00
}
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
2016-09-07 15:50:27 +03:00
args = append(args, nline)
2016-07-28 17:54:02 +03:00
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 {
2016-09-07 15:50:27 +03:00
args = append(args, line)
2016-07-28 17:54:02 +03:00
}
break
}
if len(args) > 0 {
cmds = append(cmds, args)
}
2016-09-07 08:56:44 +03:00
b = b[i+1:]
if len(b) > 0 {
2016-07-28 17:54:02 +03:00
goto next
} else {
goto done
}
}
}
case '*':
// resp formatted command
var si int
outer2:
2016-09-07 08:56:44 +03:00
for i := 0; i < len(b); i++ {
2016-09-07 15:50:27 +03:00
var args [][]byte
2016-09-07 08:56:44 +03:00
if b[i] == '\n' {
if b[i-1] != '\r' {
2016-07-28 17:54:02 +03:00
return nil, errInvalidMultiBulkLength
}
2016-09-07 08:56:44 +03:00
ni, err := parseInt(b[si+1 : i-1])
2016-07-28 17:54:02 +03:00
if err != nil || ni <= 0 {
return nil, errInvalidMultiBulkLength
}
2016-09-07 15:50:27 +03:00
args = make([][]byte, 0, ni)
2016-09-07 08:56:44 +03:00
for j := 0; j < ni; j++ {
2016-07-28 17:54:02 +03:00
// read bulk length
i++
2016-09-07 08:56:44 +03:00
if i < len(b) {
if b[i] != '$' {
2016-07-28 17:54:02 +03:00
return nil, &errProtocol{"expected '$', got '" +
2016-09-07 08:56:44 +03:00
string(b[i]) + "'"}
2016-07-28 17:54:02 +03:00
}
si = i
2016-09-07 08:56:44 +03:00
for ; i < len(b); i++ {
if b[i] == '\n' {
if b[i-1] != '\r' {
2016-07-28 17:54:02 +03:00
return nil, errInvalidBulkLength
}
2016-09-07 08:56:44 +03:00
ni2, err := parseInt(b[si+1 : i-1])
2016-07-28 17:54:02 +03:00
if err != nil || ni2 < 0 {
return nil, errInvalidBulkLength
}
2016-09-07 08:56:44 +03:00
if i+ni2+2 >= len(b) {
2016-07-28 17:54:02 +03:00
// not ready
break outer2
}
2016-09-07 08:56:44 +03:00
if b[i+ni2+2] != '\n' ||
b[i+ni2+1] != '\r' {
2016-07-28 17:54:02 +03:00
return nil, errInvalidBulkLength
}
2016-09-07 08:56:44 +03:00
i++
arg := b[i : i+ni2]
if len(args) == 0 {
for j := 0; j < len(arg); j++ {
if arg[j] >= 'A' && arg[j] <= 'Z' {
arg[j] += 32
}
}
}
i += ni2 + 1
2016-09-07 15:50:27 +03:00
args = append(args, arg)
2016-07-28 17:54:02 +03:00
break
}
}
}
}
if len(args) == cap(args) {
cmds = append(cmds, args)
2016-09-07 08:56:44 +03:00
b = b[i+1:]
if len(b) > 0 {
2016-07-28 17:54:02 +03:00
goto next
} else {
goto done
}
}
}
}
}
done:
2016-09-07 08:56:44 +03:00
if len(b) == 0 {
r.start = 0
r.end = 0
} else {
r.start = r.end - len(b)
2016-07-28 17:54:02 +03:00
}
if len(cmds) > 0 {
return cmds, nil
}
}
2016-09-07 08:56:44 +03:00
if r.end == len(r.buf) {
if len(r.buf) == 0 {
r.buf = make([]byte, r.buflen)
} else {
nbuf := make([]byte, len(r.buf)*2)
copy(nbuf, r.buf)
r.buf = nbuf
}
2016-08-20 19:23:39 +03:00
}
2016-09-07 08:56:44 +03:00
n, err := r.r.Read(r.buf[r.end:])
2016-07-28 17:54:02 +03:00
if err != nil {
if err == io.EOF {
2016-09-07 08:56:44 +03:00
if r.end > 0 {
2016-07-28 17:54:02 +03:00
return nil, io.ErrUnexpectedEOF
}
}
return nil, err
}
2016-09-07 08:56:44 +03:00
r.end += n
2016-07-28 17:54:02 +03:00
return r.ReadCommands()
}
2016-09-07 08:56:44 +03:00
func parseInt(b []byte) (int, error) {
switch len(b) {
case 1:
if b[0] >= '0' && b[0] <= '9' {
return int(b[0] - '0'), nil
}
case 2:
if b[0] >= '0' && b[0] <= '9' && b[1] >= '0' && b[1] <= '9' {
return int(b[0]-'0')*10 + int(b[1]-'0'), nil
}
}
var n int
for i := 0; i < len(b); i++ {
if b[i] < '0' || b[i] > '9' {
return 0, errors.New("invalid number")
}
n = n*10 + int(b[i]-'0')
}
return n, nil
}
2016-07-28 17:54:02 +03:00
var errClosed = errors.New("closed")
2016-08-20 18:19:08 +03:00
type writer struct {
w *net.TCPConn
2016-08-20 17:23:16 +03:00
b []byte
2016-07-28 17:54:02 +03:00
err error
}
2016-08-20 18:19:08 +03:00
func newWriter(w *net.TCPConn) *writer {
2016-08-20 20:42:34 +03:00
return &writer{w: w, b: make([]byte, 0, 512)}
2016-07-28 17:54:02 +03:00
}
2016-08-20 18:19:08 +03:00
func (w *writer) WriteNull() error {
2016-07-28 17:54:02 +03:00
if w.err != nil {
return w.err
}
2016-08-20 17:23:16 +03:00
w.b = append(w.b, '$', '-', '1', '\r', '\n')
2016-07-28 17:54:02 +03:00
return nil
}
2016-08-21 19:34:23 +03:00
func (w *writer) WriteArrayStart(count int) error {
2016-07-28 17:54:02 +03:00
if w.err != nil {
return w.err
}
2016-08-20 17:23:16 +03:00
w.b = append(w.b, '*')
w.b = append(w.b, []byte(strconv.FormatInt(int64(count), 10))...)
w.b = append(w.b, '\r', '\n')
2016-07-28 17:54:02 +03:00
return nil
}
2016-08-20 18:19:08 +03:00
func (w *writer) WriteBulk(bulk string) error {
2016-07-28 17:54:02 +03:00
if w.err != nil {
return w.err
}
2016-08-20 17:23:16 +03:00
w.b = append(w.b, '$')
w.b = append(w.b, []byte(strconv.FormatInt(int64(len(bulk)), 10))...)
w.b = append(w.b, '\r', '\n')
w.b = append(w.b, []byte(bulk)...)
2016-08-23 15:54:17 +03:00
w.b = append(w.b, '\r', '\n')
return nil
}
func (w *writer) WriteBulkBytes(bulk []byte) error {
if w.err != nil {
return w.err
}
w.b = append(w.b, '$')
w.b = append(w.b, []byte(strconv.FormatInt(int64(len(bulk)), 10))...)
w.b = append(w.b, '\r', '\n')
w.b = append(w.b, bulk...)
2016-08-20 17:23:16 +03:00
w.b = append(w.b, '\r', '\n')
2016-07-28 17:54:02 +03:00
return nil
}
2016-08-20 18:19:08 +03:00
func (w *writer) Flush() error {
2016-07-28 17:54:02 +03:00
if w.err != nil {
return w.err
}
2016-08-20 17:23:16 +03:00
if len(w.b) == 0 {
2016-07-28 17:54:02 +03:00
return nil
}
2016-08-20 17:23:16 +03:00
if _, err := w.w.Write(w.b); err != nil {
2016-07-28 17:54:02 +03:00
w.err = err
return err
}
2016-08-20 17:23:16 +03:00
w.b = w.b[:0]
2016-07-28 17:54:02 +03:00
return nil
}
2016-08-20 18:19:08 +03:00
func (w *writer) WriteError(msg string) error {
2016-07-28 17:54:02 +03:00
if w.err != nil {
return w.err
}
2016-08-20 17:23:16 +03:00
w.b = append(w.b, '-')
w.b = append(w.b, []byte(msg)...)
w.b = append(w.b, '\r', '\n')
2016-07-28 17:54:02 +03:00
return nil
}
2016-08-20 18:19:08 +03:00
func (w *writer) WriteString(msg string) error {
2016-07-28 17:54:02 +03:00
if w.err != nil {
return w.err
}
2016-09-07 08:56:44 +03:00
if msg == "OK" {
w.b = append(w.b, '+', 'O', 'K', '\r', '\n')
} else {
w.b = append(w.b, '+')
w.b = append(w.b, []byte(msg)...)
w.b = append(w.b, '\r', '\n')
}
2016-07-28 17:54:02 +03:00
return nil
}
2016-08-20 18:19:08 +03:00
func (w *writer) WriteInt(num int) error {
2016-07-28 17:54:02 +03:00
if w.err != nil {
return w.err
}
2016-08-20 17:23:16 +03:00
w.b = append(w.b, ':')
w.b = append(w.b, []byte(strconv.FormatInt(int64(num), 10))...)
w.b = append(w.b, '\r', '\n')
2016-07-28 17:54:02 +03:00
return nil
}
2016-08-20 18:19:08 +03:00
func (w *writer) Close() error {
2016-07-28 17:54:02 +03:00
if w.err != nil {
return w.err
}
if err := w.Flush(); err != nil {
w.err = err
return err
}
w.err = errClosed
return nil
}