A very fast kyoto cabinet powered memcache interface http proxy caching server.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

157 lines
3.6 KiB

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)
}