Skip to content

Instantly share code, notes, and snippets.

@vdvm
Created June 23, 2015 10:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vdvm/09b2d80e8b3ece517c0e to your computer and use it in GitHub Desktop.
Save vdvm/09b2d80e8b3ece517c0e to your computer and use it in GitHub Desktop.
Distributed PHP sessions using memcached extension and coreos/etcd (PoC)
package main
import (
"bufio"
"fmt"
"net"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"github.com/coreos/go-etcd/etcd"
)
var ETCD = []string{"http://127.0.0.1:4001"}
func main() {
runtime.GOMAXPROCS(4)
kv := etcd.NewClient(ETCD)
ok := kv.SyncCluster()
if !ok {
panic("Cannot connect to etcd")
}
kv.Close()
ln, err := net.Listen("tcp", "127.0.0.1:11211")
for i := 0; err == nil; i++ {
var conn net.Conn
conn, err = ln.Accept()
go handleConnection(conn, i)
}
}
func handleConnection(conn net.Conn, i int) {
defer conn.Close()
kv := etcd.NewClient(ETCD)
duration := 0
kv.SetDialTimeout(time.Duration(duration) * time.Millisecond)
defer kv.Close()
//fmt.Printf("%d: %v <-> %v\n", i, conn.LocalAddr(), conn.RemoteAddr())
b := bufio.NewReader(conn)
for {
line, err := b.ReadBytes('\n')
if err != nil {
break
}
switch {
case strings.HasPrefix(string(line), "get "):
handleGetCommand(conn, i, kv, line)
case strings.HasPrefix(string(line), "set "):
handleSetCommand(conn, i, kv, line, b)
case strings.HasPrefix(string(line), "delete "):
handleDeleteCommand(conn, i, kv, line)
case string(line) == "quit\r\n":
conn.Close()
case string(line) == "stats\r\n":
conn.Write([]byte("END\r\n"))
case string(line) == "version\r\n":
conn.Write([]byte("VERSION 1.4.22\r\n"))
default:
fmt.Printf("%d: %v: %v", i, "Unknown command", string(line))
conn.Write([]byte("ERROR\r\n"))
}
}
//fmt.Printf("%d: closed\n", i)
}
func handleGetCommand(conn net.Conn, i int, kv *etcd.Client, line []byte) bool {
re := regexp.MustCompile(`^(get)\s(.*\w+)(?:\r\n)$`)
matches := re.FindAllString(string(line), -1)
if matches == nil {
//fmt.Printf("%d: %v\r\n", i, "CLIENT_ERROR (GET) bad command line format")
conn.Write([]byte("CLIENT_ERROR GET bad command line format\r\n"))
return false
}
items := strings.Fields(matches[0])
key := "/sesscache/" + items[1]
resp, err := kv.Get(key, false, false)
if err != nil {
conn.Write([]byte("END\r\n"))
return true
}
reply := fmt.Sprintf("VALUE %v 0 %d\r\n%v\r\nEND\r\n", items[1], len(resp.Node.Value), resp.Node.Value)
conn.Write([]byte(reply))
return true
}
func handleSetCommand(conn net.Conn, i int, kv *etcd.Client, line []byte, b *bufio.Reader) bool {
re := regexp.MustCompile(`^(set|add|replace)\s(.*\w+)\s([0-9]{1,})\s([0-9]{1,})\s([0-9]{1,})\s?([a-zA-Z0-9]\w+)?(?:\r\n)$`)
matches := re.FindAllString(string(line), -1)
if matches == nil {
//fmt.Printf("%d: %v\r\n", i, "CLIENT_ERROR (SET1) bad command line format")
conn.Write([]byte("CLIENT_ERROR bad command line format\r\n"))
return false
}
items := strings.Fields(matches[0])
key := "/sesscache/" + items[1]
ttt, _ := strconv.Atoi(items[3])
ttl := uint64(ttt)
bytes, _ := strconv.Atoi(items[4])
//b := bufio.NewReader(conn)
line2, err := b.ReadBytes('\n')
if err != nil {
return false
}
re2 := regexp.MustCompile(`^(.*)(?:\r\n)$`)
matches2 := re2.FindAllString(string(line2), -1)
if matches2 == nil {
//fmt.Printf("%d: %v\r\n", i, "CLIENT_ERROR (SET2) bad command line format")
conn.Write([]byte("CLIENT_ERROR bad command line format\r\n"))
return false
}
items2 := strings.Fields(matches2[0])
value := string(items2[0])
if len(value) != bytes {
//fmt.Printf("%d: %v\r\n", i, "CLIENT_ERROR bad data chunk")
conn.Write([]byte("CLIENT_ERROR bad bad data chunk\r\n"))
return false
}
if _, err := kv.Set(key, value, ttl); err != nil {
//fmt.Printf("%d: %v: %v", i, "SERVER_ERROR", err)
conn.Write([]byte("SERVER_ERROR ...\r\n"))
return false
}
conn.Write([]byte("STORED\r\n"))
return true
}
func handleDeleteCommand(conn net.Conn, i int, kv *etcd.Client, line []byte) bool {
re := regexp.MustCompile(`^(delete)\s(.*\w+)\s?([a-zA-Z0-9]\w+)?(?:\r\n)$`)
matches := re.FindAllString(string(line), -1)
if matches == nil {
//fmt.Printf("%d: %v", i, "CLIENT_ERROR (DELETE) bad command line format")
conn.Write([]byte("CLIENT_ERROR bad command line format\r\n"))
return false
}
items := strings.Fields(matches[0])
key := "/sesscache/" + items[1]
_, err := kv.Delete(key, false)
if err != nil {
conn.Write([]byte("NOT_FOUND\r\n"))
return true
}
conn.Write([]byte("DELETED\r\n"))
return true
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment