resp

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2022 License: MIT Imports: 11 Imported by: 17

README

This project has been archived. If you are looking for a high-performance Redis server for Go, please checkout Redcon. It's much faster than this implementation and can handle pipelining.

RESP

GoDoc

RESP is a Go library that provides a reader, writer, and server implementation for the Redis RESP Protocol.

RESP is short for REdis Serialization Protocol. While the protocol was designed specifically for Redis, it can be used for other client-server software projects.

The RESP protocol has the advantages of being human readable and with performance of a binary protocol.

Features

Installation

Install resp using the "go get" command:

go get github.com/tidwall/resp

The Go distribution is Resp's only dependency.

Documentation

Server

A Redis clone that implements the SET and GET commands.

  • You can interact using the Redis CLI (redis-cli). http://redis.io/download
  • Or, use the telnet by typing in "telnet localhost 6380" and type in "set key value" and "get key".
  • Or, use a client library such as http://github.com/garyburd/redigo
  • The "QUIT" command will close the connection.
package main

import (
    "errors"
    "log"
    "sync"
    "github.com/tidwall/resp"
)

func main() {
    var mu sync.RWMutex
    kvs := make(map[string]string)
    s := resp.NewServer()
    s.HandleFunc("set", func(conn *resp.Conn, args []resp.Value) bool {
        if len(args) != 3 {
            conn.WriteError(errors.New("ERR wrong number of arguments for 'set' command"))
        } else {
            mu.Lock()
            kvs[args[1].String()] = args[2].String()
            mu.Unlock()
            conn.WriteSimpleString("OK")
        }
        return true
    })
    s.HandleFunc("get", func(conn *resp.Conn, args []resp.Value) bool {
        if len(args) != 2 {
            conn.WriteError(errors.New("ERR wrong number of arguments for 'get' command"))
        } else {
            mu.RLock()
            s, ok := kvs[args[1].String()]
            mu.RUnlock()
            if !ok {
                conn.WriteNull()
            } else {
                conn.WriteString(s)
            }
        }
        return true
    })
    if err := s.ListenAndServe(":6379"); err != nil {
        log.Fatal(err)
    }
}

Reader

The resp Reader type allows for an application to read raw RESP values from a file, network, or byte stream.

raw := "*3\r\n$3\r\nset\r\n$6\r\nleader\r\n$7\r\nCharlie\r\n"
raw += "*3\r\n$3\r\nset\r\n$8\r\nfollower\r\n$6\r\nSkyler\r\n"
rd := resp.NewReader(bytes.NewBufferString(raw))
for {
    v, _, err := rd.ReadValue()
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Read %s\n", v.Type())
    if v.Type() == Array {
        for i, v := range v.Array() {
            fmt.Printf("  #%d %s, value: '%s'\n", i, v.Type(), v)
        }
    }
}
// Output:
// Read Array
//   #0 BulkString, value: 'set'
//   #1 BulkString, value: 'leader'
//   #2 BulkString, value: 'Charlie'
// Read Array
//   #0 BulkString, value: 'set'
//   #1 BulkString, value: 'follower'
//   #2 BulkString, value: 'Skyler'

Writer

The resp Writer type allows for an application to write raw RESP values to a file, network, or byte stream.

var buf bytes.Buffer
wr := resp.NewWriter(&buf)
wr.WriteArray([]resp.Value{resp.StringValue("set"), resp.StringValue("leader"), resp.StringValue("Charlie")})
wr.WriteArray([]resp.Value{resp.StringValue("set"), resp.StringValue("follower"), resp.StringValue("Skyler")})
fmt.Printf("%s", buf.String())
// Output:
// *3\r\n$3\r\nset\r\n$6\r\nleader\r\n$7\r\nCharlie\r\n
// *3\r\n$3\r\nset\r\n$8\r\nfollower\r\n$6\r\nSkyler\r\n

Append-Only File

An append only file (AOF) allows your application to persist values to disk. It's very easy to use, and includes the same level of durablilty and binary format as Redis AOF Persistence.

Check out the AOF documentation for more information

// create and fill an appendonly file
aof, err := resp.OpenAOF("appendonly.aof")
if err != nil {
    log.Fatal(err)
}
// append a couple values and close the file
aof.Append(resp.MultiBulkValue("set", "leader", "Charlie"))
aof.Append(resp.MultiBulkValue("set", "follower", "Skyler"))
aof.Close()

// reopen and scan all values
aof, err = resp.OpenAOF("appendonly.aof")
if err != nil {
    log.Fatal(err)
}
defer aof.Close()
aof.Scan(func(v Value) {
    fmt.Printf("%s\n", v.String())
})

// Output:
// [set leader Charlie]
// [set follower Skyler]
}

Clients

There are bunches of RESP Clients. Most any client that supports Redis will support this implementation.

Contact

Josh Baker @tidwall

License

Tile38 source code is available under the MIT License.

Documentation

Overview

Package resp provides a reader, writer, and server implementation for the RESP protocol. http://redis.io/topics/protocol

RESP is short for "REdis Serialization Protocol". While the protocol was designed specifically for Redis, it can be used for other client-server software projects.

RESP has the advantages of being human readable and with performance of a binary protocol.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AOF

type AOF struct {
	// contains filtered or unexported fields
}

AOF represents an open file descriptor.

Example
os.RemoveAll("appendonly.aof")

// create and fill an appendonly file
aof, err := OpenAOF("appendonly.aof")
if err != nil {
	log.Fatal(err)
}
// append a couple values and close the file
aof.Append(MultiBulkValue("set", "leader", "Charlie"))
aof.Append(MultiBulkValue("set", "follower", "Skyler"))
aof.Close()

// reopen and scan all values
aof, err = OpenAOF("appendonly.aof")
if err != nil {
	log.Fatal(err)
}
defer aof.Close()
aof.Scan(func(v Value) {
	fmt.Printf("%s\n", v.String())
})
Output:

[set leader Charlie]
[set follower Skyler]

func OpenAOF

func OpenAOF(path string) (*AOF, error)

OpenAOF will open and return an AOF file. If the file does not exist a new one will be created.

func (*AOF) Append

func (aof *AOF) Append(v Value) error

Append writes a value to the end of the file.

func (*AOF) AppendMulti

func (aof *AOF) AppendMulti(vs []Value) error

AppendMulti writes multiple values to the end of the file. This operation can increase performance over calling multiple Append()s and also has the benefit of transactional writes.

func (*AOF) Close

func (aof *AOF) Close() error

Close will close the file.

func (*AOF) Scan

func (aof *AOF) Scan(iterator func(v Value)) error

Scan iterates though all values in the file. This operation could take a long time if there lots of values, and the operation cannot be canceled part way through.

func (*AOF) SetSyncPolicy

func (aof *AOF) SetSyncPolicy(policy SyncPolicy)

SetSyncPolicy set the sync policy of the file. The policy 'EverySecond' means that fsync will be called every second or so. This is fast enough, and at most you can lose one second of data if there is a disaster. The policy 'Never' means fsync is never called, the Operating System will be in charge of your data. This is the fastest and less safe method. The policy 'Always' means fsync is called after every write. This is super duper safe and very incredibly slow. EverySecond is the default.

type Conn

type Conn struct {
	*Reader
	*Writer

	RemoteAddr string
	// contains filtered or unexported fields
}

Conn represents a RESP network connection.

func NewConn

func NewConn(conn net.Conn) *Conn

NewConn returns a Conn.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader is a specialized RESP Value type reader.

Example
raw := "*3\r\n$3\r\nset\r\n$6\r\nleader\r\n$7\r\nCharlie\r\n"
raw += "*3\r\n$3\r\nset\r\n$8\r\nfollower\r\n$6\r\nSkyler\r\n"
rd := NewReader(bytes.NewBufferString(raw))
for {
	v, _, err := rd.ReadValue()
	if err == io.EOF {
		break
	}
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Read %s\n", v.Type())
	if v.Type() == Array {
		for i, v := range v.Array() {
			fmt.Printf("  #%d %s, value: '%s'\n", i, v.Type(), v)
		}
	}
}
Output:

Read Array
  #0 BulkString, value: 'set'
  #1 BulkString, value: 'leader'
  #2 BulkString, value: 'Charlie'
Read Array
  #0 BulkString, value: 'set'
  #1 BulkString, value: 'follower'
  #2 BulkString, value: 'Skyler'

func NewReader

func NewReader(rd io.Reader) *Reader

NewReader returns a Reader for reading Value types.

func (*Reader) ReadMultiBulk

func (rd *Reader) ReadMultiBulk() (value Value, telnet bool, n int, err error)

ReadMultiBulk reads the next multi bulk Value from Reader. A multi bulk value is a RESP array that contains one or more bulk strings. For more information on RESP arrays and strings please see http://redis.io/topics/protocol.

func (*Reader) ReadValue

func (rd *Reader) ReadValue() (value Value, n int, err error)

ReadValue reads the next Value from Reader.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server represents a RESP server which handles reading RESP Values.

Example
// ExampleServer is a Redis clone that implements the SET and GET commands.
// The server runs on port 6380.
// You can interact using the Redis CLI (redis-cli) The http://redis.io/download.
// Or, use the telnet by typing in "telnet localhost 6380" and type in "set key value" and "get key".
// Or, use a client library such as "http://github.com/garyburd/redigo"
// The "QUIT" command will close the connection.
var mu sync.RWMutex
kvs := make(map[string]string)
s := NewServer()
s.HandleFunc("set", func(conn *Conn, args []Value) bool {
	if len(args) != 3 {
		conn.WriteError(errors.New("ERR wrong number of arguments for 'set' command"))
	} else {
		mu.Lock()
		kvs[args[1].String()] = args[2].String()
		mu.Unlock()
		conn.WriteSimpleString("OK")
	}
	return true
})
s.HandleFunc("get", func(conn *Conn, args []Value) bool {
	if len(args) != 2 {
		conn.WriteError(errors.New("ERR wrong number of arguments for 'get' command"))
	} else {
		mu.RLock()
		s, ok := kvs[args[1].String()]
		mu.RUnlock()
		if !ok {
			conn.WriteNull()
		} else {
			conn.WriteString(s)
		}
	}
	return true
})
if err := s.ListenAndServe(":6380"); err != nil {
	log.Fatal(err)
}
Output:

func NewServer

func NewServer() *Server

NewServer returns a new Server.

func (*Server) AcceptFunc

func (s *Server) AcceptFunc(accept func(conn *Conn) bool)

AcceptFunc registers a function for accepting connections. Calling this function is optional and it allows for total control over reading and writing RESP Values from and to the connections. Returning false will close the connection.

func (*Server) HandleFunc

func (s *Server) HandleFunc(command string, handler func(conn *Conn, args []Value) bool)

HandleFunc registers the handler function for the given command. The conn parameter is a Conn type and it can be used to read and write further RESP messages from and to the connection. Returning false will close the connection.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe(addr string) error

ListenAndServe listens on the TCP network address addr for incoming connections.

type SyncPolicy

type SyncPolicy int

SyncPolicy represents a file's fsync policy.

const (
	Never       SyncPolicy = iota // The policy 'Never' means fsync is never called, the Operating System will be in charge of your data. This is the fastest and less safe method.
	EverySecond SyncPolicy = iota // The policy 'EverySecond' means that fsync will be called every second or so. This is fast enough, and at most you can lose one second of data if there is a disaster.
	Always      SyncPolicy = iota // The policy 'Always' means fsync is called after every write. This is super duper safe and very incredibly slow.
)

func (SyncPolicy) String

func (policy SyncPolicy) String() string

String returns a string respesentation.

type Type

type Type byte

Type represents a Value type

const (
	SimpleString Type = '+'
	Error        Type = '-'
	Integer      Type = ':'
	BulkString   Type = '$'
	Array        Type = '*'
)

func (Type) String

func (t Type) String() string

TypeName returns name of the underlying RESP type.

type Value

type Value struct {
	// contains filtered or unexported fields
}

Value represents the data of a valid RESP type.

func AnyValue

func AnyValue(v interface{}) Value

AnyValue returns a RESP value from an interface. This function infers the types. Arrays are not allowed.

func ArrayValue

func ArrayValue(vals []Value) Value

ArrayValue returns a RESP array.

func BoolValue

func BoolValue(t bool) Value

BoolValue returns a RESP integer representation of a bool.

func BytesValue

func BytesValue(b []byte) Value

BytesValue returns a RESP bulk string. A bulk string can represent any data.

func ErrorValue

func ErrorValue(err error) Value

ErrorValue returns a RESP error.

func FloatValue

func FloatValue(f float64) Value

FloatValue returns a RESP bulk string representation of a float.

func IntegerValue

func IntegerValue(i int) Value

IntegerValue returns a RESP integer.

func MultiBulkValue

func MultiBulkValue(commandName string, args ...interface{}) Value

MultiBulkValue returns a RESP array which contains one or more bulk strings. For more information on RESP arrays and strings please see http://redis.io/topics/protocol.

func NullValue

func NullValue() Value

NullValue returns a RESP null bulk string.

func SimpleStringValue

func SimpleStringValue(s string) Value

SimpleStringValue returns a RESP simple string. A simple string has no new lines. The carriage return and new line characters are replaced with spaces.

func StringValue

func StringValue(s string) Value

StringValue returns a RESP bulk string. A bulk string can represent any data.

func (Value) Array

func (v Value) Array() []Value

Array converts the Value to a an array. If Value is not an array or when it's is a RESP Null value, nil is returned.

func (Value) Bool

func (v Value) Bool() bool

Bool converts Value to an bool. If Value cannot be converted, false is returned.

func (Value) Bytes

func (v Value) Bytes() []byte

Bytes converts the Value to a byte array. An empty string is converted to a non-nil empty byte array. If it's a RESP Null value, nil is returned.

func (Value) Equals

func (v Value) Equals(value Value) bool

Equals compares one value to another value.

func (Value) Error

func (v Value) Error() error

Error converts the Value to an error. If Value is not an error, nil is returned.

func (Value) Float

func (v Value) Float() float64

Float converts Value to a float64. If Value cannot be converted, Zero is returned.

func (Value) Integer

func (v Value) Integer() int

Integer converts Value to an int. If Value cannot be converted, Zero is returned.

func (Value) IsNull

func (v Value) IsNull() bool

IsNull indicates whether or not the base value is null.

func (Value) MarshalRESP

func (v Value) MarshalRESP() ([]byte, error)

MarshalRESP returns the original serialized byte representation of Value. For more information on this format please see http://redis.io/topics/protocol.

func (Value) String

func (v Value) String() string

String converts Value to a string.

func (Value) Type

func (v Value) Type() Type

Type returns the underlying RESP type. The following types are represent valid RESP values.

'+'  SimpleString
'-'  Error
':'  Integer
'$'  BulkString
'*'  Array

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer is a specialized RESP Value type writer.

Example
var buf bytes.Buffer
wr := NewWriter(&buf)
wr.WriteArray([]Value{StringValue("set"), StringValue("leader"), StringValue("Charlie")})
wr.WriteArray([]Value{StringValue("set"), StringValue("follower"), StringValue("Skyler")})
fmt.Printf("%s", strings.Replace(buf.String(), "\r\n", "\\r\\n", -1))
Output:

*3\r\n$3\r\nset\r\n$6\r\nleader\r\n$7\r\nCharlie\r\n*3\r\n$3\r\nset\r\n$8\r\nfollower\r\n$6\r\nSkyler\r\n

func NewWriter

func NewWriter(wr io.Writer) *Writer

NewWriter returns a new Writer.

func (*Writer) WriteArray

func (wr *Writer) WriteArray(vals []Value) error

WriteArray writes a RESP array.

func (*Writer) WriteBytes

func (wr *Writer) WriteBytes(b []byte) error

WriteBytes writes a RESP bulk string. A bulk string can represent any data.

func (*Writer) WriteError

func (wr *Writer) WriteError(err error) error

WriteError writes a RESP error.

func (*Writer) WriteInteger

func (wr *Writer) WriteInteger(i int) error

WriteInteger writes a RESP integer.

func (*Writer) WriteMultiBulk

func (wr *Writer) WriteMultiBulk(commandName string, args ...interface{}) error

WriteMultiBulk writes a RESP array which contains one or more bulk strings. For more information on RESP arrays and strings please see http://redis.io/topics/protocol.

func (*Writer) WriteNull

func (wr *Writer) WriteNull() error

WriteNull writes a RESP null bulk string.

func (*Writer) WriteSimpleString

func (wr *Writer) WriteSimpleString(s string) error

WriteSimpleString writes a RESP simple string. A simple string has no new lines. The carriage return and new line characters are replaced with spaces.

func (*Writer) WriteString

func (wr *Writer) WriteString(s string) error

WriteString writes a RESP bulk string. A bulk string can represent any data.

func (*Writer) WriteValue

func (wr *Writer) WriteValue(v Value) error

WriteValue writes a RESP Value.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL