package server
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/garyburd/redigo/redis"
|
|
"math/rand"
|
|
"net"
|
|
"strconv"
|
|
)
|
|
|
|
type ClientConnection struct {
|
|
conn net.Conn
|
|
reader *bufio.Reader
|
|
pool *redis.Pool
|
|
redisConn redis.Conn
|
|
}
|
|
|
|
func (client *ClientConnection) ObtainRedisConnection() {
|
|
client.redisConn = client.pool.Get()
|
|
}
|
|
|
|
func (client *ClientConnection) ReleaseRedisConnection() {
|
|
client.redisConn.Close()
|
|
client.redisConn = nil
|
|
}
|
|
|
|
func (client *ClientConnection) ReadLine() (line []byte, err error) {
|
|
line, err = client.reader.ReadBytes('\n')
|
|
line = bytes.Trim(line, "\r\n ")
|
|
return line, err
|
|
}
|
|
|
|
func (client *ClientConnection) Error() (num int, err error) {
|
|
return client.conn.Write([]byte("ERROR\r\n"))
|
|
}
|
|
|
|
func (client *ClientConnection) ClientError(msg []byte) (num int, err error) {
|
|
response := append([]byte("CLIENT_ERROR "), msg...)
|
|
response = append(response, '\r', '\n')
|
|
return client.conn.Write(response)
|
|
}
|
|
|
|
func (client *ClientConnection) ServerError(msg []byte) (num int, err error) {
|
|
response := append([]byte("Server_ERROR "), msg...)
|
|
response = append(response, '\r', '\n')
|
|
return client.conn.Write(response)
|
|
}
|
|
|
|
func (client *ClientConnection) SendData(data []byte) (num int, err error) {
|
|
data = append(data, '\r', '\n')
|
|
return client.conn.Write(data)
|
|
}
|
|
|
|
func (client *ClientConnection) SendValue(key []byte, value []byte) (num int, err error) {
|
|
response := append([]byte("VALUE "), key...)
|
|
size := strconv.Itoa(len(value))
|
|
|
|
response = append(response, []byte(" 0 ")...)
|
|
response = append(response, []byte(size)...)
|
|
response = append(response, '\r', '\n')
|
|
response = append(response, value...)
|
|
response = append(response, '\r', '\n')
|
|
return client.conn.Write(response)
|
|
}
|
|
|
|
func (client *ClientConnection) SendStat(key []byte, value []byte) (num int, err error) {
|
|
response := append([]byte("STAT "), key...)
|
|
response = append(response, ' ')
|
|
response = bytes.Replace(response, []byte(":"), []byte("."), -1)
|
|
response = append(response, value...)
|
|
response = append(response, '\r', '\n')
|
|
return client.conn.Write(response)
|
|
}
|
|
|
|
func (client *ClientConnection) SendEnd() (num int, err error) {
|
|
return client.conn.Write([]byte("END\r\n"))
|
|
}
|
|
|
|
func (client *ClientConnection) SendStored() (num int, err error) {
|
|
return client.conn.Write([]byte("STORED\r\n"))
|
|
}
|
|
|
|
func (client *ClientConnection) SendNotStored() (num int, err error) {
|
|
return client.conn.Write([]byte("NOT_STORED\r\n"))
|
|
}
|
|
|
|
func (client *ClientConnection) SendDeleted() (num int, err error) {
|
|
return client.conn.Write([]byte("DELETED\r\n"))
|
|
}
|
|
|
|
func (client *ClientConnection) SendTouched() (num int, err error) {
|
|
return client.conn.Write([]byte("TOUCHED\r\n"))
|
|
}
|
|
|
|
func (client *ClientConnection) SendNotFound() (num int, err error) {
|
|
return client.conn.Write([]byte("NOT_FOUND\r\n"))
|
|
}
|
|
|
|
func (client *ClientConnection) GetExperiment(id uint64, active bool) (data Experiment, err error) {
|
|
dummy := Experiment{Id: id}
|
|
raw, err := client.redisConn.Do("GET", dummy.Key())
|
|
if err != nil {
|
|
fmt.Printf("%+v\r\n", err)
|
|
} else if raw != nil {
|
|
err = json.Unmarshal(raw.([]byte), &data)
|
|
if err == nil && data.Id == 0 {
|
|
data.Id = id
|
|
}
|
|
|
|
if err == nil && active {
|
|
raw, err := client.redisConn.Do("SISMEMBER", "active_experiments", id)
|
|
if err != nil || int(raw.(int64)) == 0 {
|
|
return data, errors.New("Experiment is not active")
|
|
}
|
|
}
|
|
|
|
} else {
|
|
return data, errors.New("No Experiment Found")
|
|
}
|
|
return data, err
|
|
}
|
|
|
|
func (client *ClientConnection) GetUserBucket(experiment Experiment, userId []byte, assign bool) (data Variant, err error) {
|
|
userKey := append([]byte("user:"), userId...)
|
|
raw, err := client.redisConn.Do("HGET", userKey, experiment.Key())
|
|
if err != nil {
|
|
fmt.Printf("%+v\r\n", err)
|
|
} else if raw != nil {
|
|
variantId, err := strconv.ParseUint(string(raw.([]byte)), 10, 64)
|
|
if err == nil {
|
|
for _, variant := range experiment.Variants {
|
|
if variant.Id == variantId {
|
|
data = variant
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if assign && data.Id == 0 {
|
|
idx := rand.Intn(len(experiment.Variants))
|
|
data = experiment.Variants[idx]
|
|
_, err = client.redisConn.Do("HSET", userKey, experiment.Key(), data.Id)
|
|
_, err = client.redisConn.Do("SADD", experiment.BucketUniqueKey(data.Id), userId)
|
|
}
|
|
|
|
_, err = client.redisConn.Do("INCR", experiment.BucketImpressionsKey(data.Id))
|
|
|
|
return data, err
|
|
}
|
|
|
|
func (client *ClientConnection) ConvertUser(experimentId uint64, userId []byte) (err error) {
|
|
experiment, err := client.GetExperiment(experimentId, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bucket, err := client.GetUserBucket(experiment, userId, false)
|
|
|
|
if bucket.Id != 0 {
|
|
_, err = client.redisConn.Do("SADD", experiment.ConvertUniqueKey(bucket.Id), userId)
|
|
_, err = client.redisConn.Do("INCR", experiment.ConvertImpressionsKey(bucket.Id))
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (client *ClientConnection) GetActiveExperimentIds() (data [][]byte, err error) {
|
|
raw, err := client.redisConn.Do("SMEMBERS", "active_experiments")
|
|
|
|
for _, id := range raw.([]interface{}) {
|
|
data = append(data, id.([]byte))
|
|
}
|
|
|
|
return data, err
|
|
}
|
|
|
|
func (client *ClientConnection) GetAllExperimentIds() (data [][]byte, err error) {
|
|
raw, err := client.redisConn.Do("KEYS", "experiment:*")
|
|
|
|
for _, key := range raw.([]interface{}) {
|
|
parts := bytes.Split(key.([]byte), []byte(":"))
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
data = append(data, parts[1])
|
|
}
|
|
|
|
return data, err
|
|
}
|
|
|
|
func (client *ClientConnection) GetExperimentStats(experimentId uint64) (err error) {
|
|
experiment, err := client.GetExperiment(experimentId, false)
|
|
|
|
for _, variant := range experiment.Variants {
|
|
raw, _ := client.redisConn.Do("SCARD", experiment.BucketUniqueKey(variant.Id))
|
|
if raw == nil {
|
|
raw = int64(0)
|
|
}
|
|
client.SendStat(experiment.BucketUniqueKey(variant.Id), []byte(strconv.Itoa(int(raw.(int64)))))
|
|
|
|
raw, _ = client.redisConn.Do("GET", experiment.BucketImpressionsKey(variant.Id))
|
|
if raw == nil {
|
|
raw = []uint8{0}
|
|
}
|
|
client.SendStat(experiment.BucketImpressionsKey(variant.Id), raw.([]uint8))
|
|
|
|
raw, _ = client.redisConn.Do("SCARD", experiment.ConvertUniqueKey(variant.Id))
|
|
if raw == nil {
|
|
raw = int64(0)
|
|
}
|
|
client.SendStat(experiment.ConvertUniqueKey(variant.Id), []byte(strconv.Itoa(int(raw.(int64)))))
|
|
|
|
raw, _ = client.redisConn.Do("GET", experiment.ConvertImpressionsKey(variant.Id))
|
|
if raw == nil {
|
|
raw = []uint8{0}
|
|
}
|
|
client.SendStat(experiment.ConvertImpressionsKey(variant.Id), raw.([]uint8))
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (client *ClientConnection) AddNewExperiment(data []byte) (err error) {
|
|
err = json.Unmarshal(data, new(interface{}))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
raw, err := client.redisConn.Do("INCR", "experiments")
|
|
nextId := uint64(raw.(int64))
|
|
|
|
dummy := Experiment{Id: nextId}
|
|
_, err = client.redisConn.Do("SET", dummy.Key(), data)
|
|
return err
|
|
}
|
|
|
|
func (client *ClientConnection) UpdateExperiment(experimentId uint64, data []byte) (err error) {
|
|
err = json.Unmarshal(data, new(interface{}))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
experiment, err := client.GetExperiment(experimentId, false)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = client.redisConn.Do("SET", experiment.Key(), data)
|
|
return err
|
|
}
|
|
|
|
func (client *ClientConnection) DeactivateExperiment(experimentId uint64) (err error) {
|
|
_, err = client.redisConn.Do("SREM", "active_experiments", experimentId)
|
|
return err
|
|
}
|
|
|
|
func (client *ClientConnection) ActivateExperiment(experimentId uint64) (err error) {
|
|
_, err = client.redisConn.Do("SADD", "active_experiments", experimentId)
|
|
return err
|
|
}
|