|
|
|
@ -0,0 +1,157 @@ |
|
|
|
package commands |
|
|
|
|
|
|
|
import ( |
|
|
|
"bitbucket.org/ww/cabinet" |
|
|
|
"bytes" |
|
|
|
"fmt" |
|
|
|
"github.com/brettlangdon/ferrite/common" |
|
|
|
"github.com/brettlangdon/ferrite/proxy" |
|
|
|
"net/url" |
|
|
|
"os" |
|
|
|
"strconv" |
|
|
|
"strings" |
|
|
|
"sync/atomic" |
|
|
|
"time" |
|
|
|
) |
|
|
|
|
|
|
|
func ParseCommand(request string) (string, []string) { |
|
|
|
request = strings.ToLower(request) |
|
|
|
tokens := strings.Split(strings.Trim(request, " \r\n"), " ") |
|
|
|
var command string |
|
|
|
if len(tokens) > 0 { |
|
|
|
command = tokens[0] |
|
|
|
} |
|
|
|
return command, tokens |
|
|
|
} |
|
|
|
|
|
|
|
func HandleCommand(command string, tokens []string, db *cabinet.KCDB) string { |
|
|
|
var result string |
|
|
|
switch command { |
|
|
|
case "get": |
|
|
|
result = HandleGet(tokens[1:], db) |
|
|
|
break |
|
|
|
case "stats": |
|
|
|
result = HandleStats(db) |
|
|
|
break |
|
|
|
case "flush_all": |
|
|
|
result = HandleFlushAll(db) |
|
|
|
break |
|
|
|
case "delete": |
|
|
|
result = HandleDelete(tokens[1:], db) |
|
|
|
break |
|
|
|
case "version": |
|
|
|
result = HandleVersion() |
|
|
|
break |
|
|
|
default: |
|
|
|
result = "ERROR\r\n" |
|
|
|
break |
|
|
|
} |
|
|
|
|
|
|
|
return result |
|
|
|
} |
|
|
|
|
|
|
|
func HandleGet(tokens []string, db *cabinet.KCDB) string { |
|
|
|
var out string |
|
|
|
if len(tokens) >= 1 { |
|
|
|
key := tokens[0] |
|
|
|
if strings.HasPrefix(key, "http%3a%2f%2f") || strings.HasPrefix(key, "https%3a%2f%2f") { |
|
|
|
key, _ = url.QueryUnescape(key) |
|
|
|
} |
|
|
|
result, err := db.Get([]byte(key)) |
|
|
|
if err != nil { |
|
|
|
atomic.AddInt32(&common.MISSES, 1) |
|
|
|
err := db.Set([]byte(key), []byte("0")) |
|
|
|
if err == nil { |
|
|
|
go proxy.CallProxy(key, db) |
|
|
|
} else { |
|
|
|
fmt.Fprintf(os.Stderr, "Error Setting Key \"%s\" Value \"0\": $s\r\n", key, err.Error()) |
|
|
|
} |
|
|
|
out = "END\r\n" |
|
|
|
} else if bytes.Equal(result, []byte("0")) { |
|
|
|
atomic.AddInt32(&common.MISSES, 1) |
|
|
|
out = "END\r\n" |
|
|
|
} else { |
|
|
|
parts := strings.SplitN(string(result), ":", 2) |
|
|
|
var expire int64 = 0 |
|
|
|
if len(parts) == 2 { |
|
|
|
expire, err = strconv.ParseInt(parts[0], 10, 32) |
|
|
|
if err != nil { |
|
|
|
expire = 0 |
|
|
|
fmt.Fprintf(os.Stderr, "Error Parsing Expiration \"%s\": %s\r\n", parts[0], err.Error()) |
|
|
|
} |
|
|
|
result = []byte(parts[1]) |
|
|
|
} |
|
|
|
now := int64(time.Now().Unix()) |
|
|
|
if expire > 0 && expire < now { |
|
|
|
err := db.Set([]byte(key), []byte("0")) |
|
|
|
if err == nil { |
|
|
|
go proxy.CallProxy(key, db) |
|
|
|
} else { |
|
|
|
fmt.Fprintf(os.Stderr, "Error Setting Key \"%s\" Value \"0\": $s\r\n", key, err.Error()) |
|
|
|
} |
|
|
|
atomic.AddInt32(&common.MISSES, 1) |
|
|
|
out = "END\r\n" |
|
|
|
} else { |
|
|
|
atomic.AddInt32(&common.HITS, 1) |
|
|
|
out = fmt.Sprintf("VALUE %s 0 %d\r\n%s\r\nEND\r\n", key, len(result), result) |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
out = "ERROR\r\n" |
|
|
|
} |
|
|
|
return out |
|
|
|
} |
|
|
|
|
|
|
|
func HandleStats(db *cabinet.KCDB) string { |
|
|
|
hits := atomic.LoadInt32(&common.HITS) |
|
|
|
misses := atomic.LoadInt32(&common.MISSES) |
|
|
|
connections := atomic.LoadInt32(&common.CONNECTIONS) |
|
|
|
var hit_ratio float32 = 0 |
|
|
|
if hits > 0 { |
|
|
|
hit_ratio = float32(hits) / float32(hits+misses) |
|
|
|
} |
|
|
|
|
|
|
|
db_stats := "" |
|
|
|
status, err := db.Status() |
|
|
|
if err == nil { |
|
|
|
lines := strings.Split(status, "\n") |
|
|
|
for _, line := range lines { |
|
|
|
parts := strings.SplitN(strings.Trim(line, "\r\n"), "\t", 2) |
|
|
|
if len(parts) == 2 { |
|
|
|
db_stats += fmt.Sprintf("STAT %s %s\r\n", parts[0], parts[1]) |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
fmt.Fprintf(os.Stderr, "Error Retrieving DB Status: %s\r\n", err.Error()) |
|
|
|
} |
|
|
|
return fmt.Sprintf("STAT hits %d\r\n"+ |
|
|
|
"STAT misses %d\r\n"+ |
|
|
|
"STAT hit_ratio %1.4f\r\n"+ |
|
|
|
"STAT connections %d\r\n"+ |
|
|
|
"%s"+ |
|
|
|
"END\r\n", |
|
|
|
hits, misses, hit_ratio, connections, db_stats, |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
func HandleFlushAll(db *cabinet.KCDB) string { |
|
|
|
db.Clear() |
|
|
|
return "OK\r\n" |
|
|
|
} |
|
|
|
|
|
|
|
func HandleDelete(tokens []string, db *cabinet.KCDB) string { |
|
|
|
if len(tokens) <= 0 { |
|
|
|
return "ERROR\r\n" |
|
|
|
} |
|
|
|
|
|
|
|
key := tokens[0] |
|
|
|
err := db.Remove([]byte(key)) |
|
|
|
if err != nil { |
|
|
|
return "NOT_FOUND\r\n" |
|
|
|
} |
|
|
|
return "DELETED\r\n" |
|
|
|
} |
|
|
|
|
|
|
|
func HandleVersion() string { |
|
|
|
return fmt.Sprintf("VERSION %s\r\n", common.VERSION) |
|
|
|
} |