From 4e2bbf1c33988ca9587140249a5241c28d74b80e Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Tue, 29 Mar 2016 05:53:53 -0700 Subject: [PATCH] resp config --- controller/config.go | 101 +++++++++++++++++++++++---------- controller/controller.go | 66 ++++++++++++--------- controller/readonly.go | 48 +++++++++------- controller/server/anyreader.go | 2 +- controller/server/server.go | 41 +++++++++++++ 5 files changed, 180 insertions(+), 78 deletions(-) diff --git a/controller/config.go b/controller/config.go index 2e1f6e9e..2b1fd8d7 100644 --- a/controller/config.go +++ b/controller/config.go @@ -1,15 +1,19 @@ package controller import ( - "bytes" "encoding/json" "fmt" "io/ioutil" "os" "strings" "time" + + "github.com/tidwall/resp" + "github.com/tidwall/tile38/controller/server" ) +var validProperties = []string{"requirepass", "leaderauth", "protected-mode"} + // Config is a tile38 config type Config struct { FollowHost string `json:"follow_host,omitempty"` @@ -82,6 +86,16 @@ func (c *Controller) setConfigProperty(name, value string, fromLoad bool) error return nil } +func (c *Controller) getConfigProperties(pattern string) map[string]interface{} { + m := make(map[string]interface{}) + for _, name := range validProperties { + matched, _ := globMatch(pattern, name) + if matched { + m[name] = c.getConfigProperty(name) + } + } + return m +} func (c *Controller) getConfigProperty(name string) string { switch name { default: @@ -127,36 +141,63 @@ func (c *Controller) writeConfig(writeProperties bool) error { return nil } -func (c *Controller) cmdConfig(line string) (string, error) { - var start = time.Now() - var cmd, name, value string - if line, cmd = token(line); cmd == "" { +func (c *Controller) cmdConfigGet(msg *server.Message) (res string, err error) { + start := time.Now() + vs := msg.Values[1:] + var ok bool + var name string + if vs, name, ok = tokenval(vs); !ok { return "", errInvalidNumberOfArguments } - var buf bytes.Buffer - buf.WriteString(`{"ok":true`) - switch strings.ToLower(cmd) { - default: - return "", errInvalidArgument(cmd) - case "get": - if line, name = token(line); name == "" || line != "" { - return "", errInvalidNumberOfArguments - } - value = c.getConfigProperty(name) - buf.WriteString(`,"value":` + jsonString(value)) - case "set": - if line, name = token(line); name == "" { - return "", errInvalidNumberOfArguments - } - value = strings.TrimSpace(line) - if err := c.setConfigProperty(name, value, false); err != nil { - return "", err - } - case "rewrite": - if err := c.writeConfig(true); err != nil { - return "", err - } + if len(vs) != 0 { + return "", errInvalidNumberOfArguments } - buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") - return buf.String(), nil + m := c.getConfigProperties(name) + switch msg.OutputType { + case server.JSON: + data, err := json.Marshal(m) + if err != nil { + return "", err + } + res = `{"ok":true,"properties":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}" + case server.RESP: + vals := respValuesSimpleMap(m) + data, err := resp.ArrayValue(vals).MarshalRESP() + if err != nil { + return "", err + } + res = string(data) + } + return +} +func (c *Controller) cmdConfigSet(msg *server.Message) (res string, err error) { + start := time.Now() + vs := msg.Values[1:] + var ok bool + var name string + if vs, name, ok = tokenval(vs); !ok { + return "", errInvalidNumberOfArguments + } + var value string + if vs, value, ok = tokenval(vs); !ok { + return "", errInvalidNumberOfArguments + } + if len(vs) != 0 { + return "", errInvalidNumberOfArguments + } + if err := c.setConfigProperty(name, value, false); err != nil { + return "", err + } + return server.OKMessage(msg, start), nil +} +func (c *Controller) cmdConfigRewrite(msg *server.Message) (res string, err error) { + start := time.Now() + vs := msg.Values[1:] + if len(vs) != 0 { + return "", errInvalidNumberOfArguments + } + if err := c.writeConfig(true); err != nil { + return "", err + } + return server.OKMessage(msg, start), nil } diff --git a/controller/controller.go b/controller/controller.go index b5a47dd1..b0a9a689 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -169,13 +169,26 @@ func (c *Controller) handleInputCommand(conn *server.Conn, msg *server.Message, for _, v := range msg.Values { words = append(words, v.String()) } - start := time.Now() - writeOutput := func(res string) error { switch msg.ConnType { default: - panic(fmt.Sprintf("unsupported conn type: %v", msg.ConnType)) + err := fmt.Errorf("unsupported conn type: %v", msg.ConnType) + log.Error(err) + return err + case server.WebSocket: + return server.WriteWebSocketMessage(w, []byte(res)) + case server.HTTP: + _, err := fmt.Fprintf(w, "HTTP/1.1 200 OK\r\n"+ + "Connection: close\r\n"+ + "Content-Length: %d\r\n"+ + "Content-Type: application/json charset=utf-8\r\n"+ + "\r\n", len(res)+2) + if err != nil { + return err + } + _, err = io.WriteString(w, res+"\r\n") + return err case server.RESP: _, err := io.WriteString(w, res) return err @@ -184,7 +197,6 @@ func (c *Controller) handleInputCommand(conn *server.Conn, msg *server.Message, return err } } - // Ping. Just send back the response. No need to put through the pipeline. if msg.Command == "ping" { switch msg.OutputType { @@ -314,22 +326,9 @@ func (c *Controller) reset() { } func (c *Controller) command(msg *server.Message, w io.Writer) (res string, d commandDetailsT, err error) { - start := time.Now() - okResp := func() string { - if w != nil { - switch msg.OutputType { - case server.JSON: - return `{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}" - case server.RESP: - return "+OK\r\n" - } - } - return "" - } switch msg.Command { default: err = fmt.Errorf("unknown command '%s'", msg.Values[0]) - return // lock case "set": res, d, err = c.cmdSet(msg) @@ -359,11 +358,8 @@ func (c *Controller) command(msg *server.Message, w io.Writer) (res string, d co // case "follow": // err = c.cmdFollow(nline) // resp = okResp() - // case "config": - // resp, err = c.cmdConfig(nline) - // case "readonly": - // err = c.cmdReadOnly(nline) - // resp = okResp() + case "readonly": + res, err = c.cmdReadOnly(msg) case "stats": res, err = c.cmdStats(msg) case "server": @@ -385,12 +381,28 @@ func (c *Controller) command(msg *server.Message, w io.Writer) (res string, d co // case "aofmd5": // resp, err = c.cmdAOFMD5(nline) case "gc": + start := time.Now() go runtime.GC() - res = okResp() - // resp = okResp() - // case "aofshrink": - // go c.aofshrink() - // resp = okResp() + res = server.OKMessage(msg, start) + // case "aofshrink": + // go c.aofshrink() + // resp = okResp() + + case "config get": + res, err = c.cmdConfigGet(msg) + case "config set": + res, err = c.cmdConfigSet(msg) + case "config rewrite": + res, err = c.cmdConfigRewrite(msg) + case "config": + err = fmt.Errorf("unknown command '%s'", msg.Values[0]) + if len(msg.Values) > 1 { + command := msg.Values[0].String() + " " + msg.Values[1].String() + msg.Values[1] = resp.StringValue(command) + msg.Values = msg.Values[1:] + msg.Command = strings.ToLower(command) + return c.command(msg, w) + } } return } diff --git a/controller/readonly.go b/controller/readonly.go index 9f98a9ac..12ad6f95 100644 --- a/controller/readonly.go +++ b/controller/readonly.go @@ -2,39 +2,47 @@ package controller import ( "strings" + "time" "github.com/tidwall/tile38/controller/log" + "github.com/tidwall/tile38/controller/server" ) -func (c *Controller) cmdReadOnly(line string) error { +func (c *Controller) cmdReadOnly(msg *server.Message) (res string, err error) { + start := time.Now() + vs := msg.Values[1:] var arg string - if line, arg = token(line); arg == "" { - return errInvalidNumberOfArguments + var ok bool + if vs, arg, ok = tokenval(vs); !ok || arg == "" { + return "", errInvalidNumberOfArguments } - if line != "" { - return errInvalidNumberOfArguments + if len(vs) != 0 { + return "", errInvalidNumberOfArguments } + update := false backup := c.config switch strings.ToLower(arg) { default: - return errInvalidArgument(arg) + return "", errInvalidArgument(arg) case "yes": - if c.config.ReadOnly { - return nil - } - c.config.ReadOnly = true - log.Info("read only") - case "no": if !c.config.ReadOnly { - return nil + update = true + c.config.ReadOnly = true + log.Info("read only") + } + case "no": + if c.config.ReadOnly { + update = true + c.config.ReadOnly = false + log.Info("read write") } - c.config.ReadOnly = false - log.Info("read write") } - err := c.writeConfig(false) - if err != nil { - c.config = backup - return err + if update { + err := c.writeConfig(false) + if err != nil { + c.config = backup + return "", err + } } - return nil + return server.OKMessage(msg, start), nil } diff --git a/controller/server/anyreader.go b/controller/server/anyreader.go index b2100434..24607d40 100644 --- a/controller/server/anyreader.go +++ b/controller/server/anyreader.go @@ -288,7 +288,7 @@ func (ar *AnyReaderWriter) readHTTPMessage() (*Message, error) { if err != nil { return nil, err } - msg.OutputType = nmsg.OutputType + msg.OutputType = JSON msg.Values = nmsg.Values msg.Command = commandValues(nmsg.Values) return msg, nil diff --git a/controller/server/server.go b/controller/server/server.go index 741bcc7c..fb1c3e26 100644 --- a/controller/server/server.go +++ b/controller/server/server.go @@ -2,11 +2,13 @@ package server import ( "bufio" + "encoding/binary" "errors" "fmt" "io" "net" "strings" + "time" //"github.com/tidwall/tile38/client" @@ -123,10 +125,49 @@ func handleConn( log.Error(err) return } + } else { + conn.Write([]byte("HTTP/1.1 500 Bad Request\r\nConnection: close\r\n\r\n")) + return + } + if msg.ConnType == HTTP || msg.ConnType == WebSocket { + return } } } +func WriteWebSocketMessage(w io.Writer, data []byte) error { + var msg []byte + buf := make([]byte, 10+len(data)) + buf[0] = 129 // FIN + TEXT + if len(data) <= 125 { + buf[1] = byte(len(data)) + copy(buf[2:], data) + msg = buf[:2+len(data)] + } else if len(data) <= 0xFFFF { + buf[1] = 126 + binary.BigEndian.PutUint16(buf[2:], uint16(len(data))) + copy(buf[4:], data) + msg = buf[:4+len(data)] + } else { + buf[1] = 127 + binary.BigEndian.PutUint64(buf[2:], uint64(len(data))) + copy(buf[10:], data) + msg = buf[:10+len(data)] + } + _, err := w.Write(msg) + return err +} + +func OKMessage(msg *Message, start time.Time) string { + switch msg.OutputType { + case JSON: + return `{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}" + case RESP: + return "+OK\r\n" + } + return "" +} + //err := func() error { // command, proto, auth, err := client.ReadMessage(rd, conn) // if err != nil {