├── .gitignore ├── .travis.yml ├── AUTHORS ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── auto.go ├── auto_test.go ├── config.go ├── debug.go ├── defaultHandler.go ├── error.go ├── example ├── main.go └── simple │ └── main.go ├── handler.go ├── handler_test.go ├── parser.go ├── parser_test.go ├── reply.go ├── reply_test.go ├── request.go ├── request_test.go ├── server.go ├── server_test.go └── stack.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.test 2 | example/example 3 | example/simple/simple 4 | redis.conf 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.1.2 4 | - 1.1.1 5 | - 1.1 6 | - tip 7 | 8 | install: 9 | - export PATH=$HOME/gopath/bin:$PATH 10 | - go get -v github.com/axw/gocov 11 | - go install github.com/axw/gocov/gocov 12 | - go get -v -d ./... 13 | 14 | script: 15 | - go test -v ./... 16 | - gocov test -deps -exclude-goroot | gocov report 17 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Solomon Hykes 2 | Alex Klizhentas 3 | Guillaume J. Charmes 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Guillaume J. Charmes 2 | Alex Klizhentas 3 | Solomon Hykes 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | coverage: 2 | gocov test | gocov report 3 | annotate: 4 | FILENAME=$(shell uuidgen) 5 | gocov test > /tmp/--go-test-server-coverage.json 6 | gocov annotate /tmp/--go-test-server-coverage.json $(fn) 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/dotcloud/go-redis-server.png)](https://travis-ci.org/dotcloud/go-redis-server) 2 | 3 | Redis server protocol library 4 | ============================= 5 | 6 | There are plenty of good client implementations of the redis protocol, but not many *server* implementations. 7 | 8 | go-redis-server is a helper library for building server software capable of speaking the redis protocol. This could be 9 | an alternate implementation of redis, a custom proxy to redis, or even a completely different backend capable of 10 | "masquerading" its API as a redis database. 11 | 12 | 13 | Sample code 14 | ------------ 15 | 16 | ```go 17 | package main 18 | 19 | import ( 20 | redis "github.com/dotcloud/go-redis-server" 21 | ) 22 | 23 | type MyHandler struct { 24 | values map[string][]byte 25 | } 26 | 27 | func (h *MyHandler) GET(key string) ([]byte, error) { 28 | v := h.values[key] 29 | return v, nil 30 | } 31 | 32 | func (h *MyHandler) SET(key string, value []byte) error { 33 | h.values[key] = value 34 | return nil 35 | } 36 | 37 | func main() { 38 | handler, _ := redis.NewAutoHandler(&MyHandler{values: make(map[string][]byte)}) 39 | server := &redis.Server{Handler: handler, Addr: ":6389"} 40 | server.ListenAndServe() 41 | } 42 | ``` 43 | 44 | Copyright (c) dotCloud 2013 45 | -------------------------------------------------------------------------------- /auto.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | "time" 9 | ) 10 | 11 | type CheckerFn func(request *Request) (reflect.Value, ReplyWriter) 12 | 13 | // type AutoHandler interface { 14 | // GET(key string) ([]byte, error) 15 | // SET(key string, value []byte) error 16 | // HMSET(key string, values *map[string][]byte) error 17 | // HGETALL(key string) (*map[string][]byte, error) 18 | // HGET(hash string, key string) ([]byte, error) 19 | // HSET(hash string, key string, value []byte) error 20 | // BRPOP(channel string, channels ...string) ([][]byte, error) 21 | // SUBSCRIBE(channel string, channels ...string) (*ChannelWriter, error) 22 | // DEL(key string, keys ...string) (int, error) 23 | // } 24 | 25 | func (srv *Server) createHandlerFn(autoHandler interface{}, f *reflect.Value) (HandlerFn, error) { 26 | errorType := reflect.TypeOf(srv.createHandlerFn).Out(1) 27 | mtype := f.Type() 28 | checkers, err := createCheckers(autoHandler, f) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | // Check output 34 | if mtype.NumOut() == 0 { 35 | return nil, errors.New("Not enough return values") 36 | } 37 | if mtype.NumOut() > 2 { 38 | return nil, errors.New("Too many return values") 39 | } 40 | if t := mtype.Out(mtype.NumOut() - 1); t != errorType { 41 | return nil, fmt.Errorf("Last return value must be an error (not %s)", t) 42 | } 43 | 44 | return srv.handlerFn(autoHandler, f, checkers) 45 | } 46 | 47 | func (srv *Server) handlerFn(autoHandler interface{}, f *reflect.Value, checkers []CheckerFn) (HandlerFn, error) { 48 | return func(request *Request) (ReplyWriter, error) { 49 | input := []reflect.Value{reflect.ValueOf(autoHandler)} 50 | 51 | for _, checker := range checkers { 52 | value, reply := checker(request) 53 | if reply != nil { 54 | return reply, nil 55 | } 56 | input = append(input, value) 57 | } 58 | var monitorString string 59 | if len(request.Args) > 0 { 60 | monitorString = fmt.Sprintf("%.6f [0 %s] \"%s\" \"%s\"", 61 | float64(time.Now().UTC().UnixNano())/1e9, 62 | request.Host, 63 | request.Name, 64 | bytes.Join(request.Args, []byte{'"', ' ', '"'})) 65 | } else { 66 | monitorString = fmt.Sprintf("%.6f [0 %s] \"%s\"", 67 | float64(time.Now().UTC().UnixNano())/1e9, 68 | request.Host, 69 | request.Name) 70 | } 71 | for _, c := range srv.MonitorChans { 72 | select { 73 | case c <- monitorString: 74 | default: 75 | } 76 | } 77 | Debugf("%s (connected monitors: %d)\n", monitorString, len(srv.MonitorChans)) 78 | 79 | var result []reflect.Value 80 | 81 | // If we don't have any input, it means we are dealing with a function. 82 | // Then remove the first parameter (object instance) 83 | if f.Type().NumIn() == 0 { 84 | input = []reflect.Value{} 85 | } else if f.Type().In(0).AssignableTo(reflect.TypeOf(autoHandler)) == false { 86 | // If we have at least one input, we check if the first one is an instance of our object 87 | // If it is, then remove it from the input list. 88 | input = input[1:] 89 | } 90 | 91 | if f.Type().IsVariadic() { 92 | result = f.CallSlice(input) 93 | } else { 94 | result = f.Call(input) 95 | } 96 | 97 | var ret interface{} 98 | if ierr := result[len(result)-1].Interface(); ierr != nil { 99 | // Last return value is an error, wrap it to redis error 100 | err := ierr.(error) 101 | // convert to redis error reply 102 | return NewError(err.Error()), nil 103 | } 104 | if len(result) > 1 { 105 | ret = result[0].Interface() 106 | return srv.createReply(request, ret) 107 | } 108 | return &StatusReply{code: "OK"}, nil 109 | }, nil 110 | } 111 | 112 | func hashValueReply(v HashValue) (*MultiBulkReply, error) { 113 | m := make(map[string]interface{}) 114 | for k, v := range v { 115 | m[k] = v 116 | } 117 | return MultiBulkFromMap(m), nil 118 | } 119 | 120 | func (srv *Server) createReply(r *Request, val interface{}) (ReplyWriter, error) { 121 | Debugf("CREATE REPLY: %T", val) 122 | switch v := val.(type) { 123 | case []interface{}: 124 | return &MultiBulkReply{values: v}, nil 125 | case string: 126 | return &BulkReply{value: []byte(v)}, nil 127 | case [][]byte: 128 | if v, ok := val.([]interface{}); ok { 129 | return &MultiBulkReply{values: v}, nil 130 | } 131 | m := make([]interface{}, len(v), cap(v)) 132 | for i, elem := range v { 133 | m[i] = elem 134 | } 135 | return &MultiBulkReply{values: m}, nil 136 | case []byte: 137 | return &BulkReply{value: v}, nil 138 | case HashValue: 139 | return hashValueReply(v) 140 | case map[string][]byte: 141 | return hashValueReply(v) 142 | case map[string]interface{}: 143 | return MultiBulkFromMap(v), nil 144 | case int: 145 | return &IntegerReply{number: v}, nil 146 | case *StatusReply: 147 | return v, nil 148 | case *MonitorReply: 149 | c := make(chan string) 150 | srv.MonitorChans = append(srv.MonitorChans, c) 151 | println("len monitor: ", len(srv.MonitorChans)) 152 | v.c = c 153 | return v, nil 154 | case *ChannelWriter: 155 | return v, nil 156 | case *MultiChannelWriter: 157 | println("New client") 158 | for _, mcw := range v.Chans { 159 | mcw.clientChan = r.ClientChan 160 | } 161 | return v, nil 162 | default: 163 | return nil, fmt.Errorf("Unsupported type: %s (%T)", v, v) 164 | } 165 | } 166 | 167 | func createCheckers(autoHandler interface{}, f *reflect.Value) ([]CheckerFn, error) { 168 | checkers := []CheckerFn{} 169 | mtype := f.Type() 170 | 171 | start := 0 172 | // If we are dealing with a method, start at 1 (first being the instance) 173 | // Otherwise, start at 0. 174 | if mtype.NumIn() > 0 && mtype.In(0).AssignableTo(reflect.TypeOf(autoHandler)) { 175 | start = 1 176 | } 177 | 178 | for i := start; i < mtype.NumIn(); i += 1 { 179 | switch mtype.In(i) { 180 | case reflect.TypeOf(""): 181 | checkers = append(checkers, stringChecker(i-start)) 182 | case reflect.TypeOf([]string{}): 183 | checkers = append(checkers, stringSliceChecker(i-start)) 184 | case reflect.TypeOf([]byte{}): 185 | checkers = append(checkers, byteChecker(i-start)) 186 | case reflect.TypeOf([][]byte{}): 187 | checkers = append(checkers, byteSliceChecker(i-start)) 188 | case reflect.TypeOf(map[string][]byte{}): 189 | if i != mtype.NumIn()-1 { 190 | return nil, errors.New("Map should be the last argument") 191 | } 192 | checkers = append(checkers, mapChecker(i-start)) 193 | case reflect.TypeOf(1): 194 | checkers = append(checkers, intChecker(i-start)) 195 | default: 196 | return nil, fmt.Errorf("Argument %d: wrong type %s (%s)", i, mtype.In(i), mtype.Name()) 197 | } 198 | } 199 | return checkers, nil 200 | } 201 | 202 | func stringChecker(index int) CheckerFn { 203 | return func(request *Request) (reflect.Value, ReplyWriter) { 204 | v, err := request.GetString(index) 205 | if err != nil { 206 | return reflect.ValueOf(""), err 207 | } 208 | return reflect.ValueOf(v), nil 209 | } 210 | } 211 | 212 | func stringSliceChecker(index int) CheckerFn { 213 | return func(request *Request) (reflect.Value, ReplyWriter) { 214 | if !request.HasArgument(index) { 215 | return reflect.ValueOf([]string{}), nil 216 | } else { 217 | v, err := request.GetStringSlice(index) 218 | if err != nil { 219 | return reflect.ValueOf([]string{}), err 220 | } 221 | return reflect.ValueOf(v), nil 222 | } 223 | } 224 | } 225 | 226 | func byteChecker(index int) CheckerFn { 227 | return func(request *Request) (reflect.Value, ReplyWriter) { 228 | err := request.ExpectArgument(index) 229 | if err != nil { 230 | return reflect.ValueOf([]byte{}), err 231 | } 232 | return reflect.ValueOf(request.Args[index]), nil 233 | } 234 | } 235 | 236 | func byteSliceChecker(index int) CheckerFn { 237 | return func(request *Request) (reflect.Value, ReplyWriter) { 238 | if !request.HasArgument(index) { 239 | return reflect.ValueOf([][]byte{}), nil 240 | } else { 241 | return reflect.ValueOf(request.Args[index:]), nil 242 | } 243 | } 244 | } 245 | 246 | func mapChecker(index int) CheckerFn { 247 | return func(request *Request) (reflect.Value, ReplyWriter) { 248 | m, err := request.GetMap(index) 249 | return reflect.ValueOf(m), err 250 | } 251 | } 252 | 253 | func intChecker(index int) CheckerFn { 254 | return func(request *Request) (reflect.Value, ReplyWriter) { 255 | m, err := request.GetInteger(index) 256 | return reflect.ValueOf(m), err 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /auto_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type Hash struct { 8 | values map[string][]byte 9 | } 10 | 11 | type TestHandler struct { 12 | values map[string][]byte 13 | hashValues map[string]Hash 14 | } 15 | 16 | func NewHandler() *TestHandler { 17 | return &TestHandler{ 18 | values: make(map[string][]byte), 19 | hashValues: make(map[string]Hash), 20 | } 21 | } 22 | 23 | func (h *TestHandler) GET(key string) ([]byte, error) { 24 | v, _ := h.values[key] 25 | return v, nil 26 | } 27 | 28 | func (h *TestHandler) SET(key string, value []byte) error { 29 | h.values[key] = value 30 | return nil 31 | } 32 | 33 | func (h *TestHandler) HMSET(key string, values ...[]byte) error { 34 | if len(values)%2 != 0 { 35 | return ErrWrongArgsNumber 36 | } 37 | _, exists := h.hashValues[key] 38 | if !exists { 39 | h.hashValues[key] = Hash{values: make(map[string][]byte)} 40 | } 41 | hash := h.hashValues[key] 42 | 43 | for i := 0; i < len(values); i += 2 { 44 | name := values[i] 45 | val := values[i+1] 46 | hash.values[string(name)] = val 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func (h *TestHandler) HGET(hash string, key string) ([]byte, error) { 53 | hs, exists := h.hashValues[hash] 54 | if !exists { 55 | return nil, nil 56 | } 57 | val, _ := hs.values[key] 58 | return val, nil 59 | } 60 | 61 | func (h *TestHandler) HSET(hash string, key string, value []byte) error { 62 | _, exists := h.hashValues[hash] 63 | if !exists { 64 | h.hashValues[hash] = Hash{values: make(map[string][]byte)} 65 | } 66 | h.hashValues[hash].values[key] = value 67 | return nil 68 | } 69 | 70 | func (h *TestHandler) HGETALL(hash string) (map[string][]byte, error) { 71 | hs, exists := h.hashValues[hash] 72 | if !exists { 73 | return nil, nil 74 | } 75 | return hs.values, nil 76 | } 77 | 78 | func (h *TestHandler) BRPOP(key string, params ...[]byte) ([][]byte, error) { 79 | params = append(params, []byte(key)) 80 | return params, nil 81 | } 82 | 83 | func (h *TestHandler) SUBSCRIBE(channel string, channels ...[]byte) (*ChannelWriter, error) { 84 | output := make(chan []interface{}) 85 | writer := &ChannelWriter{ 86 | FirstReply: []interface{}{ 87 | []byte("subscribe"), // []byte 88 | channel, // string 89 | 1, // int 90 | }, 91 | Channel: output, 92 | } 93 | go func() { 94 | output <- []interface{}{ 95 | []byte("message"), 96 | channel, 97 | []byte("yo"), 98 | } 99 | close(output) 100 | }() 101 | return writer, nil 102 | } 103 | 104 | func (h *TestHandler) DEL(key string, keys ...[]byte) (int, error) { 105 | var deleted int 106 | deleteKey := func(k string) { 107 | _, exists := h.values[k] 108 | if exists { 109 | deleted += 1 110 | delete(h.values, k) 111 | } 112 | _, exists = h.hashValues[k] 113 | if exists { 114 | deleted += 1 115 | delete(h.hashValues, k) 116 | } 117 | } 118 | 119 | deleteKey(key) 120 | for _, k := range keys { 121 | deleteKey(string(k)) 122 | } 123 | 124 | return deleted, nil 125 | } 126 | 127 | func TestAutoHandler(t *testing.T) { 128 | srv, err := NewServer(DefaultConfig().Handler(NewHandler())) 129 | if err != nil { 130 | t.Fatalf("Unexpected error: %s", err) 131 | } 132 | expected := []struct { 133 | request *Request 134 | expected []string 135 | }{ 136 | { 137 | request: &Request{ 138 | Name: "GET", 139 | Args: [][]byte{[]byte("key")}, 140 | }, 141 | expected: []string{"$-1\r\n"}, 142 | }, 143 | { 144 | request: &Request{ 145 | Name: "SET", 146 | Args: [][]byte{ 147 | []byte("key"), 148 | []byte("value"), 149 | }, 150 | }, 151 | expected: []string{"+OK\r\n"}, 152 | }, 153 | { 154 | request: &Request{ 155 | Name: "GET", 156 | Args: [][]byte{[]byte("key")}, 157 | }, 158 | expected: []string{"$5\r\nvalue\r\n"}, 159 | }, 160 | { 161 | request: &Request{ 162 | Name: "HGET", 163 | Args: [][]byte{ 164 | []byte("hkey"), 165 | []byte("prop1"), 166 | }, 167 | }, 168 | expected: []string{"$-1\r\n"}, 169 | }, 170 | { 171 | request: &Request{ 172 | Name: "HMSET", 173 | Args: [][]byte{ 174 | []byte("hkey"), 175 | []byte("prop1"), 176 | []byte("value1"), 177 | []byte("prop2"), 178 | []byte("value2"), 179 | }, 180 | }, 181 | expected: []string{"+OK\r\n"}, 182 | }, 183 | { 184 | request: &Request{ 185 | Name: "HGET", 186 | Args: [][]byte{ 187 | []byte("hkey"), 188 | []byte("prop1"), 189 | }, 190 | }, 191 | expected: []string{"$6\r\nvalue1\r\n"}, 192 | }, 193 | { 194 | request: &Request{ 195 | Name: "HGET", 196 | Args: [][]byte{ 197 | []byte("hkey"), 198 | []byte("prop2"), 199 | }, 200 | }, 201 | expected: []string{"$6\r\nvalue2\r\n"}, 202 | }, 203 | { 204 | request: &Request{ 205 | Name: "HGETALL", 206 | Args: [][]byte{ 207 | []byte("hkey"), 208 | }, 209 | }, 210 | expected: []string{ 211 | "*4\r\n$5\r\nprop1\r\n$6\r\nvalue1\r\n$5\r\nprop2\r\n$6\r\nvalue2\r\n", 212 | "*4\r\n$5\r\nprop2\r\n$6\r\nvalue2\r\n$5\r\nprop1\r\n$6\r\nvalue1\r\n", 213 | }, 214 | }, 215 | { 216 | request: &Request{ 217 | Name: "HSET", 218 | Args: [][]byte{ 219 | []byte("hkey"), 220 | []byte("prop1"), 221 | []byte("newvalue"), 222 | }, 223 | }, 224 | expected: []string{ 225 | "+OK\r\n", 226 | }, 227 | }, 228 | { 229 | request: &Request{ 230 | Name: "HGET", 231 | Args: [][]byte{ 232 | []byte("hkey"), 233 | []byte("prop1"), 234 | }, 235 | }, 236 | expected: []string{"$8\r\nnewvalue\r\n"}, 237 | }, 238 | { 239 | request: &Request{ 240 | Name: "DEL", 241 | Args: [][]byte{ 242 | []byte("key"), 243 | []byte("hkey"), 244 | }, 245 | }, 246 | expected: []string{":2\r\n"}, 247 | }, 248 | { 249 | request: &Request{ 250 | Name: "BRPOP", 251 | Args: [][]byte{ 252 | []byte("bkey"), 253 | }, 254 | }, 255 | expected: []string{ 256 | "*1\r\n$4\r\nbkey\r\n", 257 | }, 258 | }, 259 | { 260 | request: &Request{ 261 | Name: "BRPOP", 262 | Args: [][]byte{ 263 | []byte("key1"), 264 | []byte("key2"), 265 | }, 266 | }, 267 | expected: []string{ 268 | "*2\r\n$4\r\nkey2\r\n$4\r\nkey1\r\n", 269 | }, 270 | }, 271 | { 272 | request: &Request{ 273 | Name: "SUBSCRIBE", 274 | Args: [][]byte{ 275 | []byte("foo"), 276 | }, 277 | }, 278 | expected: []string{ 279 | "*3\r\n$9\r\nsubscribe\r\n$3\r\nfoo\r\n:1\r\n*3\r\n$7\r\nmessage\r\n$3\r\nfoo\r\n$2\r\nyo\r\n", 280 | }, 281 | }, 282 | } 283 | for _, v := range expected { 284 | c := make(chan struct{}) 285 | reply, err := srv.ApplyString(v.request) 286 | if err != nil { 287 | t.Fatalf("Unexpected error: %s", err) 288 | } 289 | match := false 290 | for _, expected := range v.expected { 291 | if reply == expected { 292 | match = true 293 | break 294 | } 295 | } 296 | if match == false { 297 | t.Fatalf("Expected one of %q, got: %q for request %q", v.expected, reply, v.request) 298 | } 299 | close(c) 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | type Config struct { 4 | proto string 5 | host string 6 | port int 7 | handler interface{} 8 | } 9 | 10 | func DefaultConfig() *Config { 11 | return &Config{ 12 | proto: "tcp", 13 | host: "127.0.0.1", 14 | port: 6389, 15 | handler: NewDefaultHandler(), 16 | } 17 | } 18 | 19 | func (c *Config) Port(p int) *Config { 20 | c.port = p 21 | return c 22 | } 23 | 24 | func (c *Config) Host(h string) *Config { 25 | c.host = h 26 | return c 27 | } 28 | 29 | func (c *Config) Proto(p string) *Config { 30 | c.proto = p 31 | return c 32 | } 33 | 34 | func (c *Config) Handler(h interface{}) *Config { 35 | c.handler = h 36 | return c 37 | } 38 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // Debug function, if the debug flag is set, then display. Do nothing otherwise 11 | // If Docker is in damon mode, also send the debug info on the socket 12 | // Convenience debug function, courtesy of http://github.com/dotcloud/docker 13 | func Debugf(format string, a ...interface{}) { 14 | if os.Getenv("DEBUG") != "" { 15 | 16 | // Retrieve the stack infos 17 | _, file, line, ok := runtime.Caller(1) 18 | if !ok { 19 | file = "" 20 | line = -1 21 | } else { 22 | file = file[strings.LastIndex(file, "/")+1:] 23 | } 24 | 25 | fmt.Fprintf(os.Stderr, fmt.Sprintf("[%d] [debug] %s:%d %s\n", os.Getpid(), file, line, format), a...) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /defaultHandler.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | type ( 11 | HashValue map[string][]byte 12 | HashHash map[string]HashValue 13 | HashSub map[string][]*ChannelWriter 14 | HashBrStack map[string]*Stack 15 | ) 16 | 17 | type Database struct { 18 | children map[int]*Database 19 | parent *Database 20 | 21 | values HashValue 22 | hvalues HashHash 23 | brstack HashBrStack 24 | 25 | sub HashSub 26 | } 27 | 28 | func NewDatabase(parent *Database) *Database { 29 | db := &Database{ 30 | values: make(HashValue), 31 | sub: make(HashSub), 32 | brstack: make(HashBrStack), 33 | children: map[int]*Database{}, 34 | parent: parent, 35 | } 36 | db.children[0] = db 37 | return db 38 | } 39 | 40 | type DefaultHandler struct { 41 | *Database 42 | currentDb int 43 | dbs map[int]*Database 44 | } 45 | 46 | func (h *DefaultHandler) Rpush(key string, value []byte, values ...[]byte) (int, error) { 47 | values = append([][]byte{value}, values...) 48 | if h.Database == nil { 49 | h.Database = NewDatabase(nil) 50 | } 51 | if _, exists := h.brstack[key]; !exists { 52 | h.brstack[key] = NewStack(key) 53 | } 54 | for _, value := range values { 55 | h.brstack[key].PushBack(value) 56 | } 57 | return h.brstack[key].Len(), nil 58 | } 59 | 60 | func (h *DefaultHandler) Brpop(key string, keys ...string) (data [][]byte, err error) { 61 | keys = append([]string{key}, keys...) 62 | if h.Database == nil { 63 | h.Database = NewDatabase(nil) 64 | } 65 | 66 | if len(keys) == 0 { 67 | return nil, ErrParseTimeout 68 | } 69 | 70 | timeout, err := strconv.Atoi(keys[len(keys)-1]) 71 | if err != nil { 72 | return nil, ErrParseTimeout 73 | } 74 | keys = keys[:len(keys)-1] 75 | 76 | var timeoutChan <-chan time.Time 77 | if timeout > 0 { 78 | timeoutChan = time.After(time.Duration(timeout) * time.Second) 79 | } else { 80 | timeoutChan = make(chan time.Time) 81 | } 82 | 83 | finishedChan := make(chan struct{}) 84 | go func() { 85 | defer close(finishedChan) 86 | selectCases := []reflect.SelectCase{} 87 | for _, k := range keys { 88 | key := string(k) 89 | if _, exists := h.brstack[key]; !exists { 90 | h.brstack[key] = NewStack(k) 91 | } 92 | selectCases = append(selectCases, reflect.SelectCase{ 93 | Dir: reflect.SelectRecv, 94 | Chan: reflect.ValueOf(h.brstack[key].Chan), 95 | }) 96 | } 97 | _, recv, _ := reflect.Select(selectCases) 98 | s, ok := recv.Interface().(*Stack) 99 | if !ok { 100 | err = fmt.Errorf("Impossible to retrieve data. Wrong type.") 101 | return 102 | } 103 | data = [][]byte{[]byte(s.Key), s.PopBack()} 104 | }() 105 | 106 | select { 107 | case <-finishedChan: 108 | return data, err 109 | case <-timeoutChan: 110 | return nil, nil 111 | } 112 | return nil, nil 113 | } 114 | 115 | func (h *DefaultHandler) Lrange(key string, start, stop int) ([][]byte, error) { 116 | if h.Database == nil { 117 | h.Database = NewDatabase(nil) 118 | } 119 | if _, exists := h.brstack[key]; !exists { 120 | h.brstack[key] = NewStack(key) 121 | } 122 | 123 | if start < 0 { 124 | if start = h.brstack[key].Len() + start; start < 0 { 125 | start = 0 126 | } 127 | } 128 | 129 | var ret [][]byte 130 | for i := start; i <= stop; i++ { 131 | if val := h.brstack[key].GetIndex(i); val != nil { 132 | ret = append(ret, val) 133 | } 134 | } 135 | return ret, nil 136 | } 137 | 138 | func (h *DefaultHandler) Lindex(key string, index int) ([]byte, error) { 139 | if h.Database == nil { 140 | h.Database = NewDatabase(nil) 141 | } 142 | if _, exists := h.brstack[key]; !exists { 143 | h.brstack[key] = NewStack(key) 144 | } 145 | return h.brstack[key].GetIndex(index), nil 146 | } 147 | 148 | func (h *DefaultHandler) Lpush(key string, value []byte, values ...[]byte) (int, error) { 149 | values = append([][]byte{value}, values...) 150 | if h.Database == nil { 151 | h.Database = NewDatabase(nil) 152 | } 153 | if _, exists := h.brstack[key]; !exists { 154 | h.brstack[key] = NewStack(key) 155 | } 156 | for _, value := range values { 157 | h.brstack[key].PushFront(value) 158 | } 159 | return h.brstack[key].Len(), nil 160 | } 161 | 162 | func (h *DefaultHandler) Blpop(key string, keys ...string) (data [][]byte, err error) { 163 | keys = append([]string{key}, keys...) 164 | if h.Database == nil { 165 | h.Database = NewDatabase(nil) 166 | } 167 | 168 | if len(keys) == 0 { 169 | return nil, ErrParseTimeout 170 | } 171 | 172 | timeout, err := strconv.Atoi(keys[len(keys)-1]) 173 | if err != nil { 174 | return nil, ErrParseTimeout 175 | } 176 | keys = keys[:len(keys)-1] 177 | 178 | var timeoutChan <-chan time.Time 179 | if timeout > 0 { 180 | timeoutChan = time.After(time.Duration(timeout) * time.Second) 181 | } else { 182 | timeoutChan = make(chan time.Time) 183 | } 184 | 185 | finishedChan := make(chan struct{}) 186 | 187 | go func() { 188 | defer close(finishedChan) 189 | selectCases := []reflect.SelectCase{} 190 | for _, k := range keys { 191 | key := string(k) 192 | if _, exists := h.brstack[key]; !exists { 193 | h.brstack[key] = NewStack(k) 194 | } 195 | selectCases = append(selectCases, reflect.SelectCase{ 196 | Dir: reflect.SelectRecv, 197 | Chan: reflect.ValueOf(h.brstack[key].Chan), 198 | }) 199 | } 200 | _, recv, _ := reflect.Select(selectCases) 201 | s, ok := recv.Interface().(*Stack) 202 | if !ok { 203 | err = fmt.Errorf("Impossible to retrieve data. Wrong type.") 204 | return 205 | } 206 | data = [][]byte{[]byte(s.Key), s.PopFront()} 207 | }() 208 | 209 | select { 210 | case <-finishedChan: 211 | return data, err 212 | case <-timeoutChan: 213 | return nil, nil 214 | } 215 | return nil, nil 216 | } 217 | 218 | func (h *DefaultHandler) Hget(key, subkey string) ([]byte, error) { 219 | if h.Database == nil || h.hvalues == nil { 220 | return nil, nil 221 | } 222 | 223 | if v, exists := h.hvalues[key]; exists { 224 | if v, exists := v[subkey]; exists { 225 | return v, nil 226 | } 227 | } 228 | return nil, nil 229 | } 230 | 231 | func (h *DefaultHandler) Hset(key, subkey string, value []byte) (int, error) { 232 | ret := 0 233 | 234 | if h.Database == nil { 235 | h.Database = NewDatabase(nil) 236 | } 237 | if _, exists := h.hvalues[key]; !exists { 238 | h.hvalues[key] = make(HashValue) 239 | ret = 1 240 | } 241 | 242 | if _, exists := h.hvalues[key][subkey]; !exists { 243 | ret = 1 244 | } 245 | 246 | h.hvalues[key][subkey] = value 247 | 248 | return ret, nil 249 | } 250 | 251 | func (h *DefaultHandler) Hgetall(key string) (HashValue, error) { 252 | if h.Database == nil || h.hvalues == nil { 253 | return nil, nil 254 | } 255 | return h.hvalues[key], nil 256 | } 257 | 258 | func (h *DefaultHandler) Get(key string) ([]byte, error) { 259 | if h.Database == nil || h.values == nil { 260 | return nil, nil 261 | } 262 | return h.values[key], nil 263 | } 264 | 265 | func (h *DefaultHandler) Set(key string, value []byte) error { 266 | if h.Database == nil { 267 | h.Database = NewDatabase(nil) 268 | } 269 | h.values[key] = value 270 | return nil 271 | } 272 | 273 | func (h *DefaultHandler) Del(key string, keys ...string) (int, error) { 274 | keys = append([]string{key}, keys...) 275 | if h.Database == nil { 276 | return 0, nil 277 | } 278 | count := 0 279 | for _, k := range keys { 280 | if _, exists := h.values[k]; exists { 281 | delete(h.values, k) 282 | count++ 283 | } 284 | if _, exists := h.hvalues[key]; exists { 285 | delete(h.hvalues, k) 286 | count++ 287 | } 288 | } 289 | return count, nil 290 | } 291 | 292 | func (h *DefaultHandler) Ping() (*StatusReply, error) { 293 | return &StatusReply{code: "PONG"}, nil 294 | } 295 | 296 | func (h *DefaultHandler) Subscribe(channels ...[]byte) (*MultiChannelWriter, error) { 297 | if h.Database == nil { 298 | h.Database = NewDatabase(nil) 299 | } 300 | ret := &MultiChannelWriter{Chans: make([]*ChannelWriter, 0, len(channels))} 301 | for _, key := range channels { 302 | Debugf("SUBSCRIBE on %s\n", key) 303 | cw := &ChannelWriter{ 304 | FirstReply: []interface{}{ 305 | "subscribe", 306 | key, 307 | 1, 308 | }, 309 | Channel: make(chan []interface{}), 310 | } 311 | if h.sub[string(key)] == nil { 312 | h.sub[string(key)] = []*ChannelWriter{cw} 313 | } else { 314 | h.sub[string(key)] = append(h.sub[string(key)], cw) 315 | } 316 | ret.Chans = append(ret.Chans, cw) 317 | } 318 | return ret, nil 319 | } 320 | 321 | func (h *DefaultHandler) Publish(key string, value []byte) (int, error) { 322 | if h.Database == nil || h.sub == nil { 323 | return 0, nil 324 | } 325 | // Debugf("Publishing %s on %s\n", value, key) 326 | v, exists := h.sub[key] 327 | if !exists { 328 | return 0, nil 329 | } 330 | i := 0 331 | for _, c := range v { 332 | select { 333 | case c.Channel <- []interface{}{ 334 | "message", 335 | key, 336 | value, 337 | }: 338 | i++ 339 | default: 340 | } 341 | } 342 | return i, nil 343 | } 344 | 345 | func (h *DefaultHandler) Select(key string) error { 346 | if h.dbs == nil { 347 | h.dbs = map[int]*Database{0: h.Database} 348 | } 349 | index, err := strconv.Atoi(key) 350 | if err != nil { 351 | return err 352 | } 353 | h.dbs[h.currentDb] = h.Database 354 | h.currentDb = index 355 | if _, exists := h.dbs[index]; !exists { 356 | println("DB not exits, create ", index) 357 | h.dbs[index] = NewDatabase(nil) 358 | } 359 | h.Database = h.dbs[index] 360 | return nil 361 | } 362 | 363 | func (h *DefaultHandler) Monitor() (*MonitorReply, error) { 364 | return &MonitorReply{}, nil 365 | } 366 | 367 | func NewDefaultHandler() *DefaultHandler { 368 | db := NewDatabase(nil) 369 | ret := &DefaultHandler{ 370 | Database: db, 371 | currentDb: 0, 372 | dbs: map[int]*Database{0: db}, 373 | } 374 | return ret 375 | } 376 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | ) 7 | 8 | var ( 9 | ErrMethodNotSupported = NewError("Method is not supported") 10 | ErrNotEnoughArgs = NewError("Not enough arguments for the command") 11 | ErrTooMuchArgs = NewError("Too many arguments for the command") 12 | ErrWrongArgsNumber = NewError("Wrong number of arguments") 13 | ErrExpectInteger = NewError("Expected integer") 14 | ErrExpectPositivInteger = NewError("Expected positive integer") 15 | ErrExpectMorePair = NewError("Expected at least one key val pair") 16 | ErrExpectEvenPair = NewError("Got uneven number of key val pairs") 17 | ) 18 | 19 | var ( 20 | ErrParseTimeout = errors.New("timeout is not an integer or out of range") 21 | ) 22 | 23 | type ErrorReply struct { 24 | code string 25 | message string 26 | } 27 | 28 | func (er *ErrorReply) WriteTo(w io.Writer) (int64, error) { 29 | n, err := w.Write([]byte("-" + er.code + " " + er.message + "\r\n")) 30 | return int64(n), err 31 | } 32 | 33 | func (er *ErrorReply) Error() string { 34 | return "-" + er.code + " " + er.message + "\r\n" 35 | } 36 | 37 | func NewError(message string) *ErrorReply { 38 | return &ErrorReply{code: "ERROR", message: message} 39 | } 40 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | redis "github.com/dotcloud/go-redis-server" 6 | ) 7 | 8 | type MyHandler struct { 9 | redis.DefaultHandler 10 | } 11 | 12 | // Test implement a new command. Non-redis standard, but it is possible. 13 | func (h *MyHandler) Test() ([]byte, error) { 14 | return []byte("Awesome custom redis command!"), nil 15 | } 16 | 17 | // Get override the DefaultHandler's method. 18 | func (h *MyHandler) Get(key string) ([]byte, error) { 19 | // However, we still can call the DefaultHandler GET method and use it. 20 | ret, err := h.DefaultHandler.Get(key) 21 | if ret == nil { 22 | return nil, err 23 | } 24 | return []byte("BEAM/" + string(ret)), err 25 | } 26 | 27 | // Test2 implement a new command. Non-redis standard, but it is possible. 28 | // This function needs to be registered. 29 | func Test2() ([]byte, error) { 30 | return []byte("Awesome custom redis command via function!"), nil 31 | } 32 | 33 | func main() { 34 | defer func() { 35 | if msg := recover(); msg != nil { 36 | fmt.Printf("Panic: %v\n", msg) 37 | } 38 | }() 39 | 40 | myhandler := &MyHandler{} 41 | srv, err := redis.NewServer(redis.DefaultConfig().Proto("unix").Host("/tmp/redis.sock").Handler(myhandler)) 42 | if err != nil { 43 | panic(err) 44 | } 45 | if err := srv.RegisterFct("test2", Test2); err != nil { 46 | panic(err) 47 | } 48 | if err := srv.ListenAndServe(); err != nil { 49 | panic(err) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /example/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | redis "github.com/dotcloud/go-redis-server" 5 | ) 6 | 7 | func main() { 8 | server, err := redis.NewServer(redis.DefaultConfig()) 9 | if err != nil { 10 | panic(err) 11 | } 12 | if err := server.ListenAndServe(); err != nil { 13 | panic(err) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | type HandlerFn func(r *Request) (ReplyWriter, error) 9 | 10 | func (srv *Server) RegisterFct(key string, f interface{}) error { 11 | v := reflect.ValueOf(f) 12 | handlerFn, err := srv.createHandlerFn(f, &v) 13 | if err != nil { 14 | return err 15 | } 16 | srv.Register(key, handlerFn) 17 | return nil 18 | } 19 | 20 | func (srv *Server) Register(name string, fn HandlerFn) { 21 | if srv.methods == nil { 22 | srv.methods = make(map[string]HandlerFn) 23 | } 24 | if fn != nil { 25 | Debugf("REGISTER: %s", strings.ToLower(name)) 26 | srv.methods[strings.ToLower(name)] = fn 27 | } 28 | } 29 | 30 | func (srv *Server) Apply(r *Request) (ReplyWriter, error) { 31 | if srv == nil || srv.methods == nil { 32 | Debugf("The method map is uninitialized") 33 | return ErrMethodNotSupported, nil 34 | } 35 | fn, exists := srv.methods[strings.ToLower(r.Name)] 36 | if !exists { 37 | return ErrMethodNotSupported, nil 38 | } 39 | return fn(r) 40 | } 41 | 42 | func (srv *Server) ApplyString(r *Request) (string, error) { 43 | reply, err := srv.Apply(r) 44 | if err != nil { 45 | return "", err 46 | } 47 | return ReplyToString(reply) 48 | } 49 | -------------------------------------------------------------------------------- /handler_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestEmptyHandler(t *testing.T) { 9 | c := make(chan struct{}) 10 | defer close(c) 11 | srv := &Server{} 12 | reply, err := srv.ApplyString(&Request{}) 13 | if err != nil { 14 | t.Fatalf("Unexpected error: %s", err) 15 | } 16 | if !strings.Contains(reply, "-ERROR") { 17 | t.Fatalf("Eexpected error reply, got: %s", err) 18 | } 19 | } 20 | 21 | func TestCustomHandler(t *testing.T) { 22 | srv, err := NewServer(DefaultConfig()) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | srv.Register("GET", func(r *Request) (ReplyWriter, error) { 27 | return &BulkReply{value: []byte("42")}, nil 28 | }) 29 | reply, err := srv.ApplyString(&Request{Name: "gEt"}) 30 | if err != nil { 31 | t.Fatalf("Unexpected error: %s", err) 32 | } 33 | expected := "$2\r\n42\r\n" 34 | if reply != expected { 35 | t.Fatalf("Eexpected reply %q, got: %q", expected, reply) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /parser.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "strings" 9 | ) 10 | 11 | func parseRequest(conn io.ReadCloser) (*Request, error) { 12 | r := bufio.NewReader(conn) 13 | // first line of redis request should be: 14 | // *CRLF 15 | line, err := r.ReadString('\n') 16 | if err != nil { 17 | return nil, err 18 | } 19 | // note that this line also protects us from negative integers 20 | var argsCount int 21 | 22 | // Multiline request: 23 | if line[0] == '*' { 24 | if _, err := fmt.Sscanf(line, "*%d\r", &argsCount); err != nil { 25 | return nil, malformed("*", line) 26 | } 27 | // All next lines are pairs of: 28 | //$ CR LF 29 | // CR LF 30 | // first argument is a command name, so just convert 31 | firstArg, err := readArgument(r) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | args := make([][]byte, argsCount-1) 37 | for i := 0; i < argsCount-1; i += 1 { 38 | if args[i], err = readArgument(r); err != nil { 39 | return nil, err 40 | } 41 | } 42 | 43 | return &Request{ 44 | Name: strings.ToLower(string(firstArg)), 45 | Args: args, 46 | Body: conn, 47 | }, nil 48 | } 49 | 50 | // Inline request: 51 | fields := strings.Split(strings.Trim(line, "\r\n"), " ") 52 | 53 | var args [][]byte 54 | if len(fields) > 1 { 55 | for _, arg := range fields[1:] { 56 | args = append(args, []byte(arg)) 57 | } 58 | } 59 | return &Request{ 60 | Name: strings.ToLower(string(fields[0])), 61 | Args: args, 62 | Body: conn, 63 | }, nil 64 | 65 | } 66 | 67 | func readArgument(r *bufio.Reader) ([]byte, error) { 68 | 69 | line, err := r.ReadString('\n') 70 | if err != nil { 71 | return nil, malformed("$", line) 72 | } 73 | var argSize int 74 | if _, err := fmt.Sscanf(line, "$%d\r", &argSize); err != nil { 75 | return nil, malformed("$", line) 76 | } 77 | 78 | // I think int is safe here as the max length of request 79 | // should be less then max int value? 80 | data, err := ioutil.ReadAll(io.LimitReader(r, int64(argSize))) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | if len(data) != argSize { 86 | return nil, malformedLength(argSize, len(data)) 87 | } 88 | 89 | // Now check for trailing CR 90 | if b, err := r.ReadByte(); err != nil || b != '\r' { 91 | return nil, malformedMissingCRLF() 92 | } 93 | 94 | // And LF 95 | if b, err := r.ReadByte(); err != nil || b != '\n' { 96 | return nil, malformedMissingCRLF() 97 | } 98 | 99 | return data, nil 100 | } 101 | 102 | func malformed(expected string, got string) error { 103 | Debugf("Mailformed request:'%s does not match %s\\r\\n'", got, expected) 104 | return fmt.Errorf("Mailformed request:'%s does not match %s\\r\\n'", got, expected) 105 | } 106 | 107 | func malformedLength(expected int, got int) error { 108 | return fmt.Errorf( 109 | "Mailformed request: argument length '%d does not match %d\\r\\n'", 110 | got, expected) 111 | } 112 | 113 | func malformedMissingCRLF() error { 114 | return fmt.Errorf("Mailformed request: line should end with \\r\\n") 115 | } 116 | -------------------------------------------------------------------------------- /parser_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "io/ioutil" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestReader(t *testing.T) { 12 | v, err := ioutil.ReadAll(r("Hello!")) 13 | if err != nil { 14 | t.Fatalf("Should have read it actually") 15 | } 16 | if string(v) != "Hello!" { 17 | t.Fatalf("Expected %s here, got %s", "Hello!", v) 18 | } 19 | } 20 | 21 | func TestParseBadRequests(t *testing.T) { 22 | requests := []string{ 23 | // malformed start line, 24 | "*hello\r\n", "*-100\r\n", 25 | //malformed arguments count 26 | "*3\r\nhi", "*3\r\nhi\r\n", "*4\r\n$1", "*4\r\n$1\r", "*4\r\n$1\n", 27 | //Corrupted argument size 28 | "*2\r\n$3\r\ngEt\r\n$what?\r\nx\r\n", 29 | //mismatched arguments count 30 | "*4\r\n$3\r\ngEt\r\n$1\r\nx\r\n", 31 | //missing trailing \r\n 32 | "*2\r\n$3\r\ngEt\r\n$1\r\nx", 33 | //missing trailing \r\n 34 | "*2\r\n$3\r\ngEt\r\n$1\r\nx\r", 35 | //lied about argument length \r\n 36 | "*2\r\n$3\r\ngEt\r\n$100\r\nx\r\n", 37 | } 38 | for _, v := range requests { 39 | _, err := parseRequest(ioutil.NopCloser(strings.NewReader(v))) 40 | if err == nil { 41 | t.Fatalf("Expected error for request [%s]", v) 42 | } 43 | } 44 | } 45 | 46 | func TestSucess(t *testing.T) { 47 | expected := []struct { 48 | r Request 49 | s string 50 | }{ 51 | {Request{Name: "a"}, "*1\r\n$1\r\na\r\n"}, 52 | {Request{Name: "get"}, "*1\r\n$3\r\ngEt\r\n"}, 53 | {Request{Name: "get", Args: b("x")}, "*2\r\n$3\r\ngEt\r\n$1\r\nx\r\n"}, 54 | {Request{Name: "set", Args: b("mykey", "myvalue")}, "*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"}, 55 | } 56 | 57 | for _, p := range expected { 58 | request, err := parseRequest(ioutil.NopCloser(strings.NewReader(p.s))) 59 | if err != nil { 60 | t.Fatalf("Un xxpected eror %s when parsting", err, p.s) 61 | } 62 | if request.Name != p.r.Name { 63 | t.Fatalf("Expected command %s, got %s", err, p.r.Name, request.Name) 64 | } 65 | if len(request.Args) != len(p.r.Args) { 66 | t.Fatalf("Args length mismatch %s, got %s", err, p.r.Args, request.Args) 67 | } 68 | for i := 0; i < len(request.Args); i += 1 { 69 | if !bytes.Equal(request.Args[i], p.r.Args[i]) { 70 | t.Fatalf("Expected args %s, got %s", err, p.r.Args, request.Args) 71 | } 72 | } 73 | } 74 | } 75 | 76 | func b(args ...string) [][]byte { 77 | arr := make([][]byte, len(args)) 78 | for i := 0; i < len(args); i += 1 { 79 | arr[i] = []byte(args[i]) 80 | } 81 | return arr 82 | } 83 | 84 | func r(request string) *bufio.Reader { 85 | return bufio.NewReader(bytes.NewReader([]byte(request))) 86 | } 87 | -------------------------------------------------------------------------------- /reply.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "reflect" 8 | "strconv" 9 | ) 10 | 11 | type ReplyWriter io.WriterTo 12 | 13 | type StatusReply struct { 14 | code string 15 | } 16 | 17 | func (r *StatusReply) WriteTo(w io.Writer) (int64, error) { 18 | n, err := w.Write([]byte("+" + r.code + "\r\n")) 19 | return int64(n), err 20 | } 21 | 22 | type IntegerReply struct { 23 | number int 24 | } 25 | 26 | func (r *IntegerReply) WriteTo(w io.Writer) (int64, error) { 27 | n, err := w.Write([]byte(":" + strconv.Itoa(r.number) + "\r\n")) 28 | return int64(n), err 29 | } 30 | 31 | type BulkReply struct { 32 | value []byte 33 | } 34 | 35 | func writeBytes(value interface{}, w io.Writer) (int64, error) { 36 | //it's a NullBulkReply 37 | if value == nil { 38 | n, err := w.Write([]byte("$-1\r\n")) 39 | return int64(n), err 40 | } 41 | switch v := value.(type) { 42 | case string: 43 | if len(v) == 0 { 44 | n, err := w.Write([]byte("$-1\r\n")) 45 | return int64(n), err 46 | } 47 | wrote, err := w.Write([]byte("$" + strconv.Itoa(len(v)) + "\r\n")) 48 | if err != nil { 49 | return int64(wrote), err 50 | } 51 | wroteBytes, err := w.Write([]byte(v)) 52 | if err != nil { 53 | return int64(wrote + wroteBytes), err 54 | } 55 | wroteCrLf, err := w.Write([]byte("\r\n")) 56 | return int64(wrote + wroteBytes + wroteCrLf), err 57 | case []byte: 58 | if len(v) == 0 { 59 | n, err := w.Write([]byte("$-1\r\n")) 60 | return int64(n), err 61 | } 62 | wrote, err := w.Write([]byte("$" + strconv.Itoa(len(v)) + "\r\n")) 63 | if err != nil { 64 | return int64(wrote), err 65 | } 66 | wroteBytes, err := w.Write(v) 67 | if err != nil { 68 | return int64(wrote + wroteBytes), err 69 | } 70 | wroteCrLf, err := w.Write([]byte("\r\n")) 71 | return int64(wrote + wroteBytes + wroteCrLf), err 72 | case int: 73 | wrote, err := w.Write([]byte(":" + strconv.Itoa(v) + "\r\n")) 74 | if err != nil { 75 | return int64(wrote), err 76 | } 77 | return int64(wrote), err 78 | } 79 | 80 | Debugf("Invalid type sent to writeBytes: %v", reflect.TypeOf(value).Name()) 81 | return 0, errors.New("Invalid type sent to writeBytes") 82 | } 83 | 84 | func (r *BulkReply) WriteTo(w io.Writer) (int64, error) { 85 | return writeBytes(r.value, w) 86 | } 87 | 88 | type MonitorReply struct { 89 | c <-chan string 90 | } 91 | 92 | func (r *MonitorReply) WriteTo(w io.Writer) (int64, error) { 93 | statusReply := &StatusReply{} 94 | totalBytes := int64(0) 95 | for line := range r.c { 96 | statusReply.code = line 97 | if n, err := statusReply.WriteTo(w); err != nil { 98 | totalBytes += n 99 | return int64(totalBytes), err 100 | } else { 101 | totalBytes += n 102 | } 103 | } 104 | return totalBytes, nil 105 | } 106 | 107 | //for nil reply in multi bulk just set []byte as nil 108 | type MultiBulkReply struct { 109 | values []interface{} 110 | } 111 | 112 | func MultiBulkFromMap(m map[string]interface{}) *MultiBulkReply { 113 | values := make([]interface{}, len(m)*2) 114 | i := 0 115 | for key, val := range m { 116 | values[i] = []byte(key) 117 | values[i+1] = val 118 | i += 2 119 | } 120 | return &MultiBulkReply{values: values} 121 | } 122 | 123 | func writeMultiBytes(values []interface{}, w io.Writer) (int64, error) { 124 | if values == nil { 125 | return 0, errors.New("Nil in multi bulk replies are not ok") 126 | } 127 | wrote, err := w.Write([]byte("*" + strconv.Itoa(len(values)) + "\r\n")) 128 | if err != nil { 129 | return int64(wrote), err 130 | } 131 | wrote64 := int64(wrote) 132 | for _, v := range values { 133 | wroteBytes, err := writeBytes(v, w) 134 | if err != nil { 135 | return wrote64 + wroteBytes, err 136 | } 137 | wrote64 += wroteBytes 138 | } 139 | return wrote64, err 140 | } 141 | 142 | func (r *MultiBulkReply) WriteTo(w io.Writer) (int64, error) { 143 | return writeMultiBytes(r.values, w) 144 | } 145 | 146 | func ReplyToString(r ReplyWriter) (string, error) { 147 | var b bytes.Buffer 148 | 149 | _, err := r.WriteTo(&b) 150 | if err != nil { 151 | return "ERROR!", err 152 | } 153 | return b.String(), nil 154 | } 155 | 156 | type MultiChannelWriter struct { 157 | Chans []*ChannelWriter 158 | } 159 | 160 | func (c *MultiChannelWriter) WriteTo(w io.Writer) (n int64, err error) { 161 | chans := make(chan struct{}, len(c.Chans)) 162 | for _, elem := range c.Chans { 163 | go func(elem io.WriterTo) { 164 | defer func() { chans <- struct{}{} }() 165 | if n2, err2 := elem.WriteTo(w); err2 != nil { 166 | n += n2 167 | err = err2 168 | return 169 | } else { 170 | n += n2 171 | } 172 | }(elem) 173 | } 174 | for i := 0; i < len(c.Chans); i++ { 175 | <-chans 176 | } 177 | return n, err 178 | } 179 | 180 | type ChannelWriter struct { 181 | FirstReply []interface{} 182 | Channel chan []interface{} 183 | clientChan chan struct{} 184 | } 185 | 186 | func (c *ChannelWriter) WriteTo(w io.Writer) (int64, error) { 187 | totalBytes, err := writeMultiBytes(c.FirstReply, w) 188 | if err != nil { 189 | return totalBytes, err 190 | } 191 | 192 | for { 193 | select { 194 | case <-c.clientChan: 195 | return totalBytes, err 196 | case reply := <-c.Channel: 197 | if reply == nil { 198 | return totalBytes, nil 199 | } else { 200 | wroteBytes, err := writeMultiBytes(reply, w) 201 | // FIXME: obvious overflow here, 202 | // Just ignore? Who cares? 203 | totalBytes += wroteBytes 204 | if err != nil { 205 | return totalBytes, err 206 | } 207 | } 208 | } 209 | } 210 | return totalBytes, nil 211 | } 212 | -------------------------------------------------------------------------------- /reply_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "testing" 8 | ) 9 | 10 | func TestWriteStatus(t *testing.T) { 11 | replies := []struct { 12 | reply ReplyWriter 13 | expected string 14 | }{ 15 | {&StatusReply{code: "OK"}, "+OK\r\n"}, 16 | {&IntegerReply{number: 42}, ":42\r\n"}, 17 | {&ErrorReply{code: "ERROR", message: "Something went wrong"}, "-ERROR Something went wrong\r\n"}, 18 | {&BulkReply{}, "$-1\r\n"}, 19 | {&BulkReply{[]byte{'h', 'e', 'l', 'l', 'o'}}, "$5\r\nhello\r\n"}, 20 | {&MultiBulkReply{[]interface{}{[]byte{'h', 'e', 'l', 'l', 'o'}}}, "*1\r\n$5\r\nhello\r\n"}, 21 | {&MultiBulkReply{[]interface{}{[]byte{'h', 'e', 'l', 'l', 'o'}, []byte{'h', 'i'}}}, "*2\r\n$5\r\nhello\r\n$2\r\nhi\r\n"}, 22 | {&MultiBulkReply{[]interface{}{nil, []byte{'h', 'e', 'l', 'l', 'o'}, nil, []byte{'h', 'i'}}}, "*4\r\n$-1\r\n$5\r\nhello\r\n$-1\r\n$2\r\nhi\r\n"}, 23 | {MultiBulkFromMap(map[string]interface{}{"hello": []byte("there"), "how": []byte("are you")}), "*4\r\n$5\r\nhello\r\n$5\r\nthere\r\n$3\r\nhow\r\n$7\r\nare you\r\n"}, 24 | } 25 | for _, p := range replies { 26 | var b bytes.Buffer 27 | n, err := p.reply.WriteTo(&b) 28 | if err != nil { 29 | t.Fatalf("Oops, unexpected %s", err) 30 | } 31 | val := b.String() 32 | if val != p.expected { 33 | t.Fatalf("Oops, expected %q, got %q instead", p.expected, val) 34 | } 35 | if n != int64(len(p.expected)) { 36 | t.Fatalf("Expected to write %d bytes, wrote %d instead", len(p.expected), n) 37 | } 38 | } 39 | } 40 | 41 | func TestWriteBytes(t *testing.T) { 42 | // Note: we test only failure here. Success is already tested. 43 | if _, err := writeBytes([]byte("Hello World!"), NewFailWriter(1)); err == nil { 44 | t.Fatal("Error expected after 1 write") 45 | } 46 | if _, err := writeBytes([]byte("Hello World!"), NewFailWriter(2)); err == nil { 47 | t.Fatal("Error expected after 2 writes") 48 | } 49 | } 50 | 51 | func TestWriteMultiBytes(t *testing.T) { 52 | // Note: we test only failure here. Success is already tested. 53 | if _, err := writeMultiBytes(nil, nil); err == nil { 54 | t.Fatal("Expect error when writing `nil`") 55 | } 56 | if _, err := writeMultiBytes([]interface{}{[]byte("Hello World!")}, NewFailWriter(1)); err == nil { 57 | t.Fatal("Error expected after 1 write") 58 | } 59 | if _, err := writeMultiBytes([]interface{}{[]byte("Hello World!")}, NewFailWriter(2)); err == nil { 60 | t.Fatal("Error expected after 2 write") 61 | } 62 | } 63 | 64 | type FailWriter struct { 65 | io.ReadWriter 66 | n int 67 | } 68 | 69 | func (fw *FailWriter) Write(buf []byte) (int, error) { 70 | fw.n -= 1 71 | if fw.n > 0 { 72 | return fw.ReadWriter.Write(buf) 73 | } 74 | return 0, errors.New("FAILED") 75 | } 76 | 77 | // NewFailWriter instanciate a new writer that will fail after n write. 78 | func NewFailWriter(n int) io.ReadWriter { 79 | w := bytes.NewBuffer([]byte{}) 80 | return &FailWriter{ 81 | ReadWriter: w, 82 | n: n, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "io" 5 | "strconv" 6 | ) 7 | 8 | type Request struct { 9 | Name string 10 | Args [][]byte 11 | Host string 12 | ClientChan chan struct{} 13 | Body io.ReadCloser 14 | } 15 | 16 | func (r *Request) HasArgument(index int) bool { 17 | return len(r.Args) >= index+1 18 | } 19 | 20 | func (r *Request) ExpectArgument(index int) ReplyWriter { 21 | if !r.HasArgument(index) { 22 | return ErrNotEnoughArgs 23 | } 24 | return nil 25 | } 26 | 27 | func (r *Request) GetString(index int) (string, ReplyWriter) { 28 | if reply := r.ExpectArgument(index); reply != nil { 29 | return "", reply 30 | } 31 | return string(r.Args[index]), nil 32 | } 33 | 34 | func (r *Request) GetInteger(index int) (int, ReplyWriter) { 35 | if reply := r.ExpectArgument(index); reply != nil { 36 | return -1, reply 37 | } 38 | i, err := strconv.Atoi(string(r.Args[index])) 39 | if err != nil { 40 | return -1, ErrExpectInteger 41 | } 42 | return i, nil 43 | } 44 | 45 | func (r *Request) GetPositiveInteger(index int) (int, ReplyWriter) { 46 | i, reply := r.GetInteger(index) 47 | if reply != nil { 48 | return -1, reply 49 | } 50 | if i < 0 { 51 | return -1, ErrExpectPositivInteger 52 | } 53 | return i, nil 54 | } 55 | 56 | func (r *Request) GetStringSlice(index int) ([]string, ReplyWriter) { 57 | if reply := r.ExpectArgument(index); reply != nil { 58 | return nil, reply 59 | } 60 | var ret []string 61 | for _, elem := range r.Args[index:] { 62 | ret = append(ret, string(elem)) 63 | } 64 | return ret, nil 65 | } 66 | 67 | func (r *Request) GetMap(index int) (map[string][]byte, ReplyWriter) { 68 | count := len(r.Args) - index 69 | if count <= 0 { 70 | return nil, ErrExpectMorePair 71 | } 72 | if count%2 != 0 { 73 | return nil, ErrExpectEvenPair 74 | } 75 | values := make(map[string][]byte) 76 | for i := index; i < len(r.Args); i += 2 { 77 | key, reply := r.GetString(i) 78 | if reply != nil { 79 | return nil, reply 80 | } 81 | values[key] = r.Args[i+1] 82 | } 83 | return values, nil 84 | } 85 | -------------------------------------------------------------------------------- /request_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestRequestExpectArgument(t *testing.T) { 9 | r := &Request{Name: "Hi", Args: [][]byte{}} 10 | for i := 0; i < 10; i += 1 { 11 | reply := r.ExpectArgument(i) 12 | if reply == nil { 13 | t.Fatalf("Expected error reply, got nil") 14 | } 15 | } 16 | r = &Request{Name: "Hi", Args: [][]byte{{'h', 'i'}}} 17 | reply := r.ExpectArgument(0) 18 | if reply != nil { 19 | t.Fatalf("Expected nil reply, got %s", reply) 20 | } 21 | 22 | reply = r.ExpectArgument(1) 23 | if reply == nil { 24 | t.Fatalf("Expected error reply, got nil") 25 | } 26 | } 27 | 28 | func TestRequestGetString(t *testing.T) { 29 | s := "Hello, World!" 30 | r := &Request{Name: "Hi", Args: [][]byte{[]byte(s)}} 31 | val, reply := r.GetString(0) 32 | if reply != nil { 33 | t.Fatalf("Expected nil reply, got %s", reply) 34 | } 35 | if val != s { 36 | t.Fatalf("Expected %s, got %s", s, val) 37 | } 38 | val, reply = r.GetString(5) 39 | if reply == nil { 40 | t.Fatalf("Expected reply, got nil") 41 | } 42 | } 43 | 44 | func TestRequestGetInteger(t *testing.T) { 45 | invalid := []*Request{ 46 | {Name: "Hi", Args: [][]byte{}}, 47 | {Name: "Hi", Args: [][]byte{{'h', 'i'}}}, 48 | } 49 | for _, request := range invalid { 50 | _, reply := request.GetInteger(0) 51 | if reply == nil { 52 | t.Fatalf("Expected error reply, got nil") 53 | } 54 | } 55 | 56 | valid := []struct { 57 | request *Request 58 | index int 59 | number int 60 | }{ 61 | {&Request{Name: "Hi", Args: [][]byte{{'1'}}}, 0, 1}, 62 | {&Request{Name: "Hi", Args: [][]byte{{'1'}, []byte("42")}}, 1, 42}, 63 | {&Request{Name: "Hi", Args: [][]byte{{'1'}, []byte("-1043")}}, 1, -1043}, 64 | } 65 | for _, v := range valid { 66 | number, reply := v.request.GetInteger(v.index) 67 | if reply != nil { 68 | t.Fatalf("Expected nil reply, got %s", reply) 69 | } 70 | if v.number != number { 71 | t.Fatalf("Expected %d reply, got %d", number, v.number) 72 | } 73 | number, reply = v.request.GetPositiveInteger(v.index) 74 | if v.number > 0 { 75 | if reply != nil { 76 | t.Fatalf("Expected nil reply, got %s", reply) 77 | } 78 | } else { 79 | if reply == nil { 80 | t.Fatalf("Expected error reply, got %s", reply) 81 | } 82 | } 83 | } 84 | } 85 | 86 | func TestRequestGetMap(t *testing.T) { 87 | invalid := []struct { 88 | request *Request 89 | index int 90 | }{ 91 | {&Request{Name: "Hi", Args: [][]byte{}}, 0}, 92 | {&Request{Name: "Hi", Args: [][]byte{}}, 100}, 93 | {&Request{Name: "Hi", Args: [][]byte{{'h', 'i'}}}, 0}, 94 | {&Request{Name: "Hi", Args: [][]byte{{'h', 'i'}, {'h', 'i'}, {'h', 'i'}}}, 0}, 95 | } 96 | for _, v := range invalid { 97 | _, reply := v.request.GetMap(v.index) 98 | if reply == nil { 99 | t.Fatalf("Expected error reply, got nil for %s %d", v.request, v.index) 100 | } 101 | } 102 | 103 | valid := []struct { 104 | request *Request 105 | index int 106 | expected map[string][]byte 107 | }{ 108 | { 109 | request: &Request{ 110 | Name: "Hi", 111 | Args: [][]byte{ 112 | {'h', 'i'}, 113 | {'y', 'o'}, 114 | }, 115 | }, 116 | index: 0, 117 | expected: map[string][]byte{"hi": []byte("yo")}, 118 | }, 119 | { 120 | request: &Request{ 121 | Name: "Hi", 122 | Args: [][]byte{ 123 | []byte("hi"), 124 | []byte("yo"), 125 | []byte("key"), 126 | []byte("value"), 127 | }, 128 | }, 129 | index: 0, 130 | expected: map[string][]byte{"hi": []byte("yo"), "key": []byte("value")}, 131 | }, 132 | } 133 | for _, v := range valid { 134 | m, reply := v.request.GetMap(v.index) 135 | if reply != nil { 136 | t.Fatalf("Expected nil reply, got %s for %s %d", reply, v.request, v.index) 137 | } 138 | if !mapsEqual(m, v.expected) { 139 | t.Fatalf("Expected %s got %s for %s", v.expected, v.request, m) 140 | } 141 | } 142 | } 143 | 144 | func mapsEqual(a map[string][]byte, b map[string][]byte) bool { 145 | if len(a) != len(b) { 146 | return false 147 | } 148 | for key, val := range a { 149 | if !bytes.Equal(b[key], val) { 150 | return false 151 | } 152 | } 153 | return true 154 | } 155 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | // go-redis-server is a helper library for building server software capable of speaking the redis protocol. 2 | // This could be an alternate implementation of redis, a custom proxy to redis, 3 | // or even a completely different backend capable of "masquerading" its API as a redis database. 4 | 5 | package redis 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "net" 12 | "reflect" 13 | ) 14 | 15 | type Server struct { 16 | Proto string 17 | Addr string // TCP address to listen on, ":6389" if empty 18 | MonitorChans []chan string 19 | methods map[string]HandlerFn 20 | } 21 | 22 | func (srv *Server) ListenAndServe() error { 23 | addr := srv.Addr 24 | if srv.Proto == "" { 25 | srv.Proto = "tcp" 26 | } 27 | if srv.Proto == "unix" && addr == "" { 28 | addr = "/tmp/redis.sock" 29 | } else if addr == "" { 30 | addr = ":6389" 31 | } 32 | l, e := net.Listen(srv.Proto, addr) 33 | if e != nil { 34 | return e 35 | } 36 | return srv.Serve(l) 37 | } 38 | 39 | // Serve accepts incoming connections on the Listener l, creating a 40 | // new service goroutine for each. The service goroutines read requests and 41 | // then call srv.Handler to reply to them. 42 | func (srv *Server) Serve(l net.Listener) error { 43 | defer l.Close() 44 | srv.MonitorChans = []chan string{} 45 | for { 46 | rw, err := l.Accept() 47 | if err != nil { 48 | return err 49 | } 50 | go srv.ServeClient(rw) 51 | } 52 | } 53 | 54 | // Serve starts a new redis session, using `conn` as a transport. 55 | // It reads commands using the redis protocol, passes them to `handler`, 56 | // and returns the result. 57 | func (srv *Server) ServeClient(conn net.Conn) (err error) { 58 | defer func() { 59 | if err != nil { 60 | fmt.Fprintf(conn, "-%s\n", err) 61 | } 62 | conn.Close() 63 | }() 64 | 65 | clientChan := make(chan struct{}) 66 | 67 | // Read on `conn` in order to detect client disconnect 68 | go func() { 69 | // Close chan in order to trigger eventual selects 70 | defer close(clientChan) 71 | defer Debugf("Client disconnected") 72 | // FIXME: move conn within the request. 73 | if false { 74 | io.Copy(ioutil.Discard, conn) 75 | } 76 | }() 77 | 78 | var clientAddr string 79 | 80 | switch co := conn.(type) { 81 | case *net.UnixConn: 82 | f, err := conn.(*net.UnixConn).File() 83 | if err != nil { 84 | return err 85 | } 86 | clientAddr = f.Name() 87 | default: 88 | clientAddr = co.RemoteAddr().String() 89 | } 90 | 91 | for { 92 | request, err := parseRequest(conn) 93 | if err != nil { 94 | return err 95 | } 96 | request.Host = clientAddr 97 | request.ClientChan = clientChan 98 | reply, err := srv.Apply(request) 99 | if err != nil { 100 | return err 101 | } 102 | if _, err = reply.WriteTo(conn); err != nil { 103 | return err 104 | } 105 | } 106 | return nil 107 | } 108 | 109 | func NewServer(c *Config) (*Server, error) { 110 | srv := &Server{ 111 | Proto: c.proto, 112 | MonitorChans: []chan string{}, 113 | methods: make(map[string]HandlerFn), 114 | } 115 | 116 | if srv.Proto == "unix" { 117 | srv.Addr = c.host 118 | } else { 119 | srv.Addr = fmt.Sprintf("%s:%d", c.host, c.port) 120 | } 121 | 122 | if c.handler == nil { 123 | c.handler = NewDefaultHandler() 124 | } 125 | 126 | rh := reflect.TypeOf(c.handler) 127 | for i := 0; i < rh.NumMethod(); i++ { 128 | method := rh.Method(i) 129 | if method.Name[0] > 'a' && method.Name[0] < 'z' { 130 | continue 131 | } 132 | println(method.Name) 133 | handlerFn, err := srv.createHandlerFn(c.handler, &method.Func) 134 | if err != nil { 135 | return nil, err 136 | } 137 | srv.Register(method.Name, handlerFn) 138 | } 139 | return srv, nil 140 | } 141 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestServer(t *testing.T) { 8 | t.Skip("Not implemented") 9 | } 10 | -------------------------------------------------------------------------------- /stack.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Stack struct { 8 | sync.Mutex 9 | Key string 10 | stack [][]byte 11 | Chan chan *Stack 12 | } 13 | 14 | func (s *Stack) PopBack() []byte { 15 | s.Lock() 16 | defer s.Unlock() 17 | 18 | if s.stack == nil || len(s.stack) == 0 { 19 | return nil 20 | } 21 | 22 | var ret []byte 23 | if len(s.stack)-1 == 0 { 24 | ret, s.stack = s.stack[0], [][]byte{} 25 | } else { 26 | ret, s.stack = s.stack[len(s.stack)-1], s.stack[:len(s.stack)-1] 27 | } 28 | return ret 29 | } 30 | 31 | func (s *Stack) PushBack(val []byte) { 32 | s.Lock() 33 | defer s.Unlock() 34 | 35 | if s.stack == nil { 36 | s.stack = [][]byte{} 37 | } 38 | 39 | go func() { 40 | if s.Chan != nil { 41 | s.Chan <- s 42 | } 43 | }() 44 | s.stack = append(s.stack, val) 45 | } 46 | 47 | func (s *Stack) PopFront() []byte { 48 | s.Lock() 49 | defer s.Unlock() 50 | 51 | if s.stack == nil || len(s.stack) == 0 { 52 | return nil 53 | } 54 | 55 | var ret []byte 56 | if len(s.stack)-1 == 0 { 57 | ret, s.stack = s.stack[0], [][]byte{} 58 | } else { 59 | ret, s.stack = s.stack[0], s.stack[1:] 60 | } 61 | return ret 62 | } 63 | 64 | func (s *Stack) PushFront(val []byte) { 65 | s.Lock() 66 | defer s.Unlock() 67 | 68 | if s.stack == nil { 69 | s.stack = [][]byte{} 70 | } 71 | 72 | s.stack = append([][]byte{val}, s.stack...) 73 | go func() { 74 | if s.Chan != nil { 75 | s.Chan <- s 76 | } 77 | }() 78 | } 79 | 80 | // GetIndex return the element at the requested index. 81 | // If no element correspond, return nil. 82 | func (s *Stack) GetIndex(index int) []byte { 83 | s.Lock() 84 | defer s.Unlock() 85 | 86 | if index < 0 { 87 | if len(s.stack)+index >= 0 { 88 | return s.stack[len(s.stack)+index] 89 | } 90 | return nil 91 | } 92 | if len(s.stack) > index { 93 | return s.stack[index] 94 | } 95 | return nil 96 | } 97 | 98 | func (s *Stack) Len() int { 99 | s.Lock() 100 | defer s.Unlock() 101 | return len(s.stack) 102 | } 103 | 104 | func NewStack(key string) *Stack { 105 | return &Stack{ 106 | stack: [][]byte{}, 107 | Chan: make(chan *Stack), 108 | Key: key, 109 | } 110 | } 111 | --------------------------------------------------------------------------------