channels
This commit is contained in:
parent
3aa394219d
commit
eef5f3c7b6
@ -133,26 +133,9 @@ func commandErrIsFatal(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) writeAOF(value resp.Value, d *commandDetailsT) error {
|
func (c *Controller) writeAOF(value resp.Value, d *commandDetailsT) error {
|
||||||
if d != nil {
|
if d != nil && !d.updated {
|
||||||
if !d.updated {
|
// just ignore writes if the command did not update
|
||||||
return nil // just ignore writes if the command did not update
|
return nil
|
||||||
}
|
|
||||||
if c.config.followHost() == "" {
|
|
||||||
// process hooks, for leader only
|
|
||||||
if d.parent {
|
|
||||||
// process children only
|
|
||||||
for _, d := range d.children {
|
|
||||||
if err := c.queueHooks(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// process parent
|
|
||||||
if err := c.queueHooks(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if c.shrinking {
|
if c.shrinking {
|
||||||
var values []string
|
var values []string
|
||||||
@ -178,14 +161,34 @@ func (c *Controller) writeAOF(value resp.Value, d *commandDetailsT) error {
|
|||||||
c.fcond.Broadcast()
|
c.fcond.Broadcast()
|
||||||
c.fcond.L.Unlock()
|
c.fcond.L.Unlock()
|
||||||
|
|
||||||
|
// process geofences
|
||||||
if d != nil {
|
if d != nil {
|
||||||
// write to live connection streams
|
// webhook geofences
|
||||||
|
if c.config.followHost() == "" {
|
||||||
|
// for leader only
|
||||||
|
if d.parent {
|
||||||
|
// queue children
|
||||||
|
for _, d := range d.children {
|
||||||
|
if err := c.queueHooks(d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// queue parent
|
||||||
|
if err := c.queueHooks(d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// live geofences
|
||||||
c.lcond.L.Lock()
|
c.lcond.L.Lock()
|
||||||
if d.parent {
|
if d.parent {
|
||||||
|
// queue children
|
||||||
for _, d := range d.children {
|
for _, d := range d.children {
|
||||||
c.lstack = append(c.lstack, d)
|
c.lstack = append(c.lstack, d)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// queue parent
|
||||||
c.lstack = append(c.lstack, d)
|
c.lstack = append(c.lstack, d)
|
||||||
}
|
}
|
||||||
c.lcond.Broadcast()
|
c.lcond.Broadcast()
|
||||||
@ -196,7 +199,7 @@ func (c *Controller) writeAOF(value resp.Value, d *commandDetailsT) error {
|
|||||||
|
|
||||||
func (c *Controller) queueHooks(d *commandDetailsT) error {
|
func (c *Controller) queueHooks(d *commandDetailsT) error {
|
||||||
// big list of all of the messages
|
// big list of all of the messages
|
||||||
var hmsgs [][]byte
|
var hmsgs []string
|
||||||
var hooks []*Hook
|
var hooks []*Hook
|
||||||
// find the hook by the key
|
// find the hook by the key
|
||||||
if hm, ok := c.hookcols[d.key]; ok {
|
if hm, ok := c.hookcols[d.key]; ok {
|
||||||
@ -204,9 +207,13 @@ func (c *Controller) queueHooks(d *commandDetailsT) error {
|
|||||||
// match the fence
|
// match the fence
|
||||||
msgs := FenceMatch(hook.Name, hook.ScanWriter, hook.Fence, hook.Metas, d)
|
msgs := FenceMatch(hook.Name, hook.ScanWriter, hook.Fence, hook.Metas, d)
|
||||||
if len(msgs) > 0 {
|
if len(msgs) > 0 {
|
||||||
// append each msg to the big list
|
if hook.channel {
|
||||||
hmsgs = append(hmsgs, msgs...)
|
c.Publish(hook.Name, msgs...)
|
||||||
hooks = append(hooks, hook)
|
} else {
|
||||||
|
// append each msg to the big list
|
||||||
|
hmsgs = append(hmsgs, msgs...)
|
||||||
|
hooks = append(hooks, hook)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,7 +266,7 @@ type liveAOFSwitches struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s liveAOFSwitches) Error() string {
|
func (s liveAOFSwitches) Error() string {
|
||||||
return "going live"
|
return goingLive
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) cmdAOFMD5(msg *server.Message) (res resp.Value, err error) {
|
func (c *Controller) cmdAOFMD5(msg *server.Message) (res resp.Value, err error) {
|
||||||
|
@ -190,17 +190,23 @@ func (c *Controller) aofshrink() {
|
|||||||
if hook == nil {
|
if hook == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hook.mu.Lock()
|
hook.cond.L.Lock()
|
||||||
defer hook.mu.Unlock()
|
defer hook.cond.L.Unlock()
|
||||||
|
|
||||||
var values []string
|
var values []string
|
||||||
values = append(values, "sethook")
|
if hook.channel {
|
||||||
values = append(values, name)
|
values = append(values, "setchan", name)
|
||||||
values = append(values, strings.Join(hook.Endpoints, ","))
|
} else {
|
||||||
|
values = append(values, "sethook", name,
|
||||||
|
strings.Join(hook.Endpoints, ","))
|
||||||
|
values = append(values)
|
||||||
|
}
|
||||||
|
for _, meta := range hook.Metas {
|
||||||
|
values = append(values, "meta", meta.Name, meta.Value)
|
||||||
|
}
|
||||||
for _, value := range hook.Message.Values {
|
for _, value := range hook.Message.Values {
|
||||||
values = append(values, value.String())
|
values = append(values, value.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the values to the aof buffer
|
// append the values to the aof buffer
|
||||||
aofbuf = append(aofbuf, '*')
|
aofbuf = append(aofbuf, '*')
|
||||||
aofbuf = append(aofbuf, strconv.FormatInt(int64(len(values)), 10)...)
|
aofbuf = append(aofbuf, strconv.FormatInt(int64(len(values)), 10)...)
|
||||||
|
@ -29,6 +29,8 @@ import (
|
|||||||
|
|
||||||
var errOOM = errors.New("OOM command not allowed when used memory > 'maxmemory'")
|
var errOOM = errors.New("OOM command not allowed when used memory > 'maxmemory'")
|
||||||
|
|
||||||
|
const goingLive = "going live"
|
||||||
|
|
||||||
const hookLogPrefix = "hook:log:"
|
const hookLogPrefix = "hook:log:"
|
||||||
|
|
||||||
type collectionT struct {
|
type collectionT struct {
|
||||||
@ -109,6 +111,8 @@ type Controller struct {
|
|||||||
aofconnM map[net.Conn]bool
|
aofconnM map[net.Conn]bool
|
||||||
luascripts *lScriptMap
|
luascripts *lScriptMap
|
||||||
luapool *lStatePool
|
luapool *lStatePool
|
||||||
|
|
||||||
|
pubsub *pubsub
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAndServe starts a new tile38 server
|
// ListenAndServe starts a new tile38 server
|
||||||
@ -136,10 +140,10 @@ func ListenAndServeEx(host string, port int, dir string, ln *net.Listener, http
|
|||||||
expires: make(map[string]map[string]time.Time),
|
expires: make(map[string]map[string]time.Time),
|
||||||
started: time.Now(),
|
started: time.Now(),
|
||||||
conns: make(map[*server.Conn]*clientConn),
|
conns: make(map[*server.Conn]*clientConn),
|
||||||
epc: endpoint.NewManager(),
|
|
||||||
http: http,
|
http: http,
|
||||||
|
pubsub: newPubsub(),
|
||||||
}
|
}
|
||||||
|
c.epc = endpoint.NewManager(c)
|
||||||
c.luascripts = c.NewScriptMap()
|
c.luascripts = c.NewScriptMap()
|
||||||
c.luapool = c.NewPool()
|
c.luapool = c.NewPool()
|
||||||
defer c.luapool.Shutdown()
|
defer c.luapool.Shutdown()
|
||||||
@ -217,7 +221,7 @@ func ListenAndServeEx(host string, port int, dir string, ln *net.Listener, http
|
|||||||
c.statsTotalCommands.add(1)
|
c.statsTotalCommands.add(1)
|
||||||
err := c.handleInputCommand(conn, msg, w)
|
err := c.handleInputCommand(conn, msg, w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "going live" {
|
if err.Error() == goingLive {
|
||||||
return c.goLive(err, conn, rd, msg, websocket)
|
return c.goLive(err, conn, rd, msg, websocket)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -490,7 +494,9 @@ func (c *Controller) handleInputCommand(conn *server.Conn, msg *server.Message,
|
|||||||
default:
|
default:
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
case "set", "del", "drop", "fset", "flushdb", "sethook", "pdelhook", "delhook",
|
case "set", "del", "drop", "fset", "flushdb",
|
||||||
|
"setchan", "pdelchan", "delchan",
|
||||||
|
"sethook", "pdelhook", "delhook",
|
||||||
"expire", "persist", "jset", "pdel":
|
"expire", "persist", "jset", "pdel":
|
||||||
// write operations
|
// write operations
|
||||||
write = true
|
write = true
|
||||||
@ -512,8 +518,9 @@ func (c *Controller) handleInputCommand(conn *server.Conn, msg *server.Message,
|
|||||||
if c.config.readOnly() {
|
if c.config.readOnly() {
|
||||||
return writeErr("read only")
|
return writeErr("read only")
|
||||||
}
|
}
|
||||||
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search",
|
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks",
|
||||||
"ttl", "bounds", "server", "info", "type", "jget", "evalro", "evalrosha":
|
"chans", "search", "ttl", "bounds", "server", "info", "type", "jget",
|
||||||
|
"evalro", "evalrosha":
|
||||||
// read operations
|
// read operations
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
@ -548,6 +555,8 @@ func (c *Controller) handleInputCommand(conn *server.Conn, msg *server.Message,
|
|||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
case "evalna", "evalnasha":
|
case "evalna", "evalnasha":
|
||||||
// No locking for scripts, otherwise writes cannot happen within scripts
|
// No locking for scripts, otherwise writes cannot happen within scripts
|
||||||
|
case "subscribe", "psubscribe", "publish":
|
||||||
|
// No locking for pubsub
|
||||||
}
|
}
|
||||||
|
|
||||||
res, d, err := c.command(msg, w, conn)
|
res, d, err := c.command(msg, w, conn)
|
||||||
@ -556,7 +565,7 @@ func (c *Controller) handleInputCommand(conn *server.Conn, msg *server.Message,
|
|||||||
return writeErr(res.String())
|
return writeErr(res.String())
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "going live" {
|
if err.Error() == goingLive {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return writeErr(err.Error())
|
return writeErr(err.Error())
|
||||||
@ -630,20 +639,31 @@ func (c *Controller) command(
|
|||||||
res, d, err = c.cmdDrop(msg)
|
res, d, err = c.cmdDrop(msg)
|
||||||
case "flushdb":
|
case "flushdb":
|
||||||
res, d, err = c.cmdFlushDB(msg)
|
res, d, err = c.cmdFlushDB(msg)
|
||||||
|
|
||||||
case "sethook":
|
case "sethook":
|
||||||
res, d, err = c.cmdSetHook(msg)
|
res, d, err = c.cmdSetHook(msg, false)
|
||||||
case "delhook":
|
case "delhook":
|
||||||
res, d, err = c.cmdDelHook(msg)
|
res, d, err = c.cmdDelHook(msg, false)
|
||||||
case "pdelhook":
|
case "pdelhook":
|
||||||
res, d, err = c.cmdPDelHook(msg)
|
res, d, err = c.cmdPDelHook(msg, false)
|
||||||
|
case "hooks":
|
||||||
|
res, err = c.cmdHooks(msg, false)
|
||||||
|
|
||||||
|
case "setchan":
|
||||||
|
res, d, err = c.cmdSetHook(msg, true)
|
||||||
|
case "delchan":
|
||||||
|
res, d, err = c.cmdDelHook(msg, true)
|
||||||
|
case "pdelchan":
|
||||||
|
res, d, err = c.cmdPDelHook(msg, true)
|
||||||
|
case "chans":
|
||||||
|
res, err = c.cmdHooks(msg, true)
|
||||||
|
|
||||||
case "expire":
|
case "expire":
|
||||||
res, d, err = c.cmdExpire(msg)
|
res, d, err = c.cmdExpire(msg)
|
||||||
case "persist":
|
case "persist":
|
||||||
res, d, err = c.cmdPersist(msg)
|
res, d, err = c.cmdPersist(msg)
|
||||||
case "ttl":
|
case "ttl":
|
||||||
res, err = c.cmdTTL(msg)
|
res, err = c.cmdTTL(msg)
|
||||||
case "hooks":
|
|
||||||
res, err = c.cmdHooks(msg)
|
|
||||||
case "shutdown":
|
case "shutdown":
|
||||||
if !core.DevMode {
|
if !core.DevMode {
|
||||||
err = fmt.Errorf("unknown command '%s'", msg.Values[0])
|
err = fmt.Errorf("unknown command '%s'", msg.Values[0])
|
||||||
@ -737,6 +757,12 @@ func (c *Controller) command(
|
|||||||
res, err = c.cmdScriptExists(msg)
|
res, err = c.cmdScriptExists(msg)
|
||||||
case "script flush":
|
case "script flush":
|
||||||
res, err = c.cmdScriptFlush(msg)
|
res, err = c.cmdScriptFlush(msg)
|
||||||
|
case "subscribe":
|
||||||
|
res, err = c.cmdSubscribe(msg)
|
||||||
|
case "psubscribe":
|
||||||
|
res, err = c.cmdPsubscribe(msg)
|
||||||
|
case "publish":
|
||||||
|
res, err = c.cmdPublish(msg)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// FenceMatch executes a fence match returns back json messages for fence detection.
|
// FenceMatch executes a fence match returns back json messages for fence detection.
|
||||||
func FenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas []FenceMeta, details *commandDetailsT) [][]byte {
|
func FenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas []FenceMeta, details *commandDetailsT) []string {
|
||||||
msgs := fenceMatch(hookName, sw, fence, metas, details)
|
msgs := fenceMatch(hookName, sw, fence, metas, details)
|
||||||
if len(fence.accept) == 0 {
|
if len(fence.accept) == 0 {
|
||||||
return msgs
|
return msgs
|
||||||
}
|
}
|
||||||
nmsgs := make([][]byte, 0, len(msgs))
|
nmsgs := make([]string, 0, len(msgs))
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
if fence.accept[gjson.GetBytes(msg, "command").String()] {
|
if fence.accept[gjson.Get(msg, "command").String()] {
|
||||||
nmsgs = append(nmsgs, msg)
|
nmsgs = append(nmsgs, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,9 +47,12 @@ func appendHookDetails(b []byte, hookName string, metas []FenceMeta) []byte {
|
|||||||
func hookJSONString(hookName string, metas []FenceMeta) string {
|
func hookJSONString(hookName string, metas []FenceMeta) string {
|
||||||
return string(appendHookDetails(nil, hookName, metas))
|
return string(appendHookDetails(nil, hookName, metas))
|
||||||
}
|
}
|
||||||
func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas []FenceMeta, details *commandDetailsT) [][]byte {
|
func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas []FenceMeta, details *commandDetailsT) []string {
|
||||||
if details.command == "drop" {
|
if details.command == "drop" {
|
||||||
return [][]byte{[]byte(`{"command":"drop"` + hookJSONString(hookName, metas) + `,"time":` + jsonTimeFormat(details.timestamp) + `}`)}
|
return []string{
|
||||||
|
`{"command":"drop"` + hookJSONString(hookName, metas) +
|
||||||
|
`,"time":` + jsonTimeFormat(details.timestamp) + `}`,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(fence.glob) > 0 && !(len(fence.glob) == 1 && fence.glob[0] == '*') {
|
if len(fence.glob) > 0 && !(len(fence.glob) == 1 && fence.glob[0] == '*') {
|
||||||
match, _ := glob.Match(fence.glob, details.id)
|
match, _ := glob.Match(fence.glob, details.id)
|
||||||
@ -69,7 +72,10 @@ func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if details.command == "del" {
|
if details.command == "del" {
|
||||||
return [][]byte{[]byte(`{"command":"del"` + hookJSONString(hookName, metas) + `,"id":` + jsonString(details.id) + `,"time":` + jsonTimeFormat(details.timestamp) + `}`)}
|
return []string{
|
||||||
|
`{"command":"del"` + hookJSONString(hookName, metas) + `,"id":` + jsonString(details.id) +
|
||||||
|
`,"time":` + jsonTimeFormat(details.timestamp) + `}`,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var roamkeys, roamids []string
|
var roamkeys, roamids []string
|
||||||
var roammeters []float64
|
var roammeters []float64
|
||||||
@ -164,14 +170,13 @@ func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make([]byte, sw.wr.Len())
|
res := sw.wr.String()
|
||||||
copy(res, sw.wr.Bytes())
|
|
||||||
sw.wr.Reset()
|
sw.wr.Reset()
|
||||||
if len(res) > 0 && res[0] == ',' {
|
if len(res) > 0 && res[0] == ',' {
|
||||||
res = res[1:]
|
res = res[1:]
|
||||||
}
|
}
|
||||||
if sw.output == outputIDs {
|
if sw.output == outputIDs {
|
||||||
res = []byte(`{"id":` + string(res) + `}`)
|
res = `{"id":` + string(res) + `}`
|
||||||
}
|
}
|
||||||
sw.mu.Unlock()
|
sw.mu.Unlock()
|
||||||
|
|
||||||
@ -195,12 +200,13 @@ func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var msgs [][]byte
|
var msgs []string
|
||||||
if fence.detect == nil || fence.detect[detect] {
|
if fence.detect == nil || fence.detect[detect] {
|
||||||
if len(res) > 0 && res[0] == '{' {
|
if len(res) > 0 && res[0] == '{' {
|
||||||
msgs = append(msgs, makemsg(details.command, group, detect, hookName, metas, details.key, details.timestamp, res[1:]))
|
msgs = append(msgs, makemsg(details.command, group, detect,
|
||||||
|
hookName, metas, details.key, details.timestamp, res[1:]))
|
||||||
} else {
|
} else {
|
||||||
msgs = append(msgs, res)
|
msgs = append(msgs, string(res))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch detect {
|
switch detect {
|
||||||
@ -214,11 +220,12 @@ func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas
|
|||||||
}
|
}
|
||||||
case "roam":
|
case "roam":
|
||||||
if len(msgs) > 0 {
|
if len(msgs) > 0 {
|
||||||
var nmsgs [][]byte
|
var nmsgs []string
|
||||||
msg := msgs[0][:len(msgs[0])-1]
|
msg := msgs[0][:len(msgs[0])-1]
|
||||||
for i, id := range roamids {
|
for i, id := range roamids {
|
||||||
|
|
||||||
nmsg := append([]byte(nil), msg...)
|
var nmsg []byte
|
||||||
|
nmsg = append(nmsg, msg...)
|
||||||
nmsg = append(nmsg, `,"nearby":{"key":`...)
|
nmsg = append(nmsg, `,"nearby":{"key":`...)
|
||||||
nmsg = appendJSONString(nmsg, roamkeys[i])
|
nmsg = appendJSONString(nmsg, roamkeys[i])
|
||||||
nmsg = append(nmsg, `,"id":`...)
|
nmsg = append(nmsg, `,"id":`...)
|
||||||
@ -261,7 +268,7 @@ func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas
|
|||||||
|
|
||||||
nmsg = append(nmsg, '}')
|
nmsg = append(nmsg, '}')
|
||||||
nmsg = append(nmsg, '}')
|
nmsg = append(nmsg, '}')
|
||||||
nmsgs = append(nmsgs, nmsg)
|
nmsgs = append(nmsgs, string(nmsg))
|
||||||
}
|
}
|
||||||
msgs = nmsgs
|
msgs = nmsgs
|
||||||
}
|
}
|
||||||
@ -269,7 +276,10 @@ func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas
|
|||||||
return msgs
|
return msgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func makemsg(command, group, detect, hookName string, metas []FenceMeta, key string, t time.Time, tail []byte) []byte {
|
func makemsg(
|
||||||
|
command, group, detect, hookName string,
|
||||||
|
metas []FenceMeta, key string, t time.Time, tail string,
|
||||||
|
) string {
|
||||||
var buf []byte
|
var buf []byte
|
||||||
buf = append(append(buf, `{"command":"`...), command...)
|
buf = append(append(buf, `{"command":"`...), command...)
|
||||||
buf = append(append(buf, `","group":"`...), group...)
|
buf = append(append(buf, `","group":"`...), group...)
|
||||||
@ -279,7 +289,7 @@ func makemsg(command, group, detect, hookName string, metas []FenceMeta, key str
|
|||||||
buf = appendJSONString(append(buf, `,"key":`...), key)
|
buf = appendJSONString(append(buf, `,"key":`...), key)
|
||||||
buf = appendJSONTimeFormat(append(buf, `,"time":`...), t)
|
buf = appendJSONTimeFormat(append(buf, `,"time":`...), t)
|
||||||
buf = append(append(buf, ','), tail...)
|
buf = append(append(buf, ','), tail...)
|
||||||
return buf
|
return string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fenceMatchObject(fence *liveFenceSwitches, obj geojson.Object) bool {
|
func fenceMatchObject(fence *liveFenceSwitches, obj geojson.Object) bool {
|
||||||
|
@ -2,7 +2,6 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@ -36,31 +35,38 @@ func (a hooksByName) Swap(i, j int) {
|
|||||||
a[i], a[j] = a[j], a[i]
|
a[i], a[j] = a[j], a[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) cmdSetHook(msg *server.Message) (res resp.Value, d commandDetailsT, err error) {
|
func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
|
||||||
|
res resp.Value, d commandDetailsT, err error,
|
||||||
|
) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
vs := msg.Values[1:]
|
vs := msg.Values[1:]
|
||||||
var name, urls, cmd string
|
var name, urls, cmd string
|
||||||
var ok bool
|
var ok bool
|
||||||
if vs, name, ok = tokenval(vs); !ok || name == "" {
|
if vs, name, ok = tokenval(vs); !ok || name == "" {
|
||||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||||
}
|
}
|
||||||
if vs, urls, ok = tokenval(vs); !ok || urls == "" {
|
|
||||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
|
||||||
}
|
|
||||||
var endpoints []string
|
var endpoints []string
|
||||||
for _, url := range strings.Split(urls, ",") {
|
if chanCmd {
|
||||||
url = strings.TrimSpace(url)
|
endpoints = []string{"local://" + name}
|
||||||
err := c.epc.Validate(url)
|
} else {
|
||||||
if err != nil {
|
if vs, urls, ok = tokenval(vs); !ok || urls == "" {
|
||||||
log.Errorf("sethook: %v", err)
|
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||||
return resp.SimpleStringValue(""), d, errInvalidArgument(url)
|
}
|
||||||
|
for _, url := range strings.Split(urls, ",") {
|
||||||
|
url = strings.TrimSpace(url)
|
||||||
|
err := c.epc.Validate(url)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("sethook: %v", err)
|
||||||
|
return resp.SimpleStringValue(""), d, errInvalidArgument(url)
|
||||||
|
}
|
||||||
|
endpoints = append(endpoints, url)
|
||||||
}
|
}
|
||||||
endpoints = append(endpoints, url)
|
|
||||||
}
|
}
|
||||||
var commandvs []resp.Value
|
var commandvs []resp.Value
|
||||||
var cmdlc string
|
var cmdlc string
|
||||||
var types []string
|
var types []string
|
||||||
|
// var expires float64
|
||||||
|
// var expiresSet bool
|
||||||
metaMap := make(map[string]string)
|
metaMap := make(map[string]string)
|
||||||
for {
|
for {
|
||||||
commandvs = vs
|
commandvs = vs
|
||||||
@ -82,6 +88,18 @@ func (c *Controller) cmdSetHook(msg *server.Message) (res resp.Value, d commandD
|
|||||||
}
|
}
|
||||||
metaMap[metakey] = metaval
|
metaMap[metakey] = metaval
|
||||||
continue
|
continue
|
||||||
|
// case "ex":
|
||||||
|
// var s string
|
||||||
|
// if vs, s, ok = tokenval(vs); !ok || s == "" {
|
||||||
|
// return server.NOMessage, d, errInvalidNumberOfArguments
|
||||||
|
// }
|
||||||
|
// v, err := strconv.ParseFloat(s, 64)
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// return server.NOMessage, d, errInvalidArgument(s)
|
||||||
|
// }
|
||||||
|
// expires = v
|
||||||
|
// expiresSet = true
|
||||||
case "nearby":
|
case "nearby":
|
||||||
types = nearbyTypes
|
types = nearbyTypes
|
||||||
case "within", "intersects":
|
case "within", "intersects":
|
||||||
@ -89,7 +107,7 @@ func (c *Controller) cmdSetHook(msg *server.Message) (res resp.Value, d commandD
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
s, err := c.cmdSearchArgs(cmdlc, vs, types)
|
s, err := c.cmdSearchArgs(true, cmdlc, vs, types)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return server.NOMessage, d, err
|
return server.NOMessage, d, err
|
||||||
@ -119,12 +137,18 @@ func (c *Controller) cmdSetHook(msg *server.Message) (res resp.Value, d commandD
|
|||||||
Endpoints: endpoints,
|
Endpoints: endpoints,
|
||||||
Fence: &s,
|
Fence: &s,
|
||||||
Message: cmsg,
|
Message: cmsg,
|
||||||
db: c.qdb,
|
|
||||||
epm: c.epc,
|
epm: c.epc,
|
||||||
Metas: metas,
|
Metas: metas,
|
||||||
|
channel: chanCmd,
|
||||||
|
cond: sync.NewCond(&sync.Mutex{}),
|
||||||
|
}
|
||||||
|
// if expiresSet {
|
||||||
|
// hook.expires =
|
||||||
|
// time.Now().Add(time.Duration(expires * float64(time.Second)))
|
||||||
|
// }
|
||||||
|
if !chanCmd {
|
||||||
|
hook.db = c.qdb
|
||||||
}
|
}
|
||||||
hook.cond = sync.NewCond(&hook.mu)
|
|
||||||
|
|
||||||
var wr bytes.Buffer
|
var wr bytes.Buffer
|
||||||
hook.ScanWriter, err = c.newScanWriter(
|
hook.ScanWriter, err = c.newScanWriter(
|
||||||
&wr, cmsg, s.key, s.output, s.precision, s.glob, false,
|
&wr, cmsg, s.key, s.output, s.precision, s.glob, false,
|
||||||
@ -134,6 +158,10 @@ func (c *Controller) cmdSetHook(msg *server.Message) (res resp.Value, d commandD
|
|||||||
}
|
}
|
||||||
|
|
||||||
if h, ok := c.hooks[name]; ok {
|
if h, ok := c.hooks[name]; ok {
|
||||||
|
if h.channel != chanCmd {
|
||||||
|
return server.NOMessage, d,
|
||||||
|
errors.New("hooks and channels cannot share the same name")
|
||||||
|
}
|
||||||
if h.Equals(hook) {
|
if h.Equals(hook) {
|
||||||
// it was a match so we do nothing. But let's signal just
|
// it was a match so we do nothing. But let's signal just
|
||||||
// for good measure.
|
// for good measure.
|
||||||
@ -171,7 +199,9 @@ func (c *Controller) cmdSetHook(msg *server.Message) (res resp.Value, d commandD
|
|||||||
return server.NOMessage, d, nil
|
return server.NOMessage, d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) cmdDelHook(msg *server.Message) (res resp.Value, d commandDetailsT, err error) {
|
func (c *Controller) cmdDelHook(msg *server.Message, chanCmd bool) (
|
||||||
|
res resp.Value, d commandDetailsT, err error,
|
||||||
|
) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
vs := msg.Values[1:]
|
vs := msg.Values[1:]
|
||||||
|
|
||||||
@ -183,7 +213,7 @@ func (c *Controller) cmdDelHook(msg *server.Message) (res resp.Value, d commandD
|
|||||||
if len(vs) != 0 {
|
if len(vs) != 0 {
|
||||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||||
}
|
}
|
||||||
if h, ok := c.hooks[name]; ok {
|
if h, ok := c.hooks[name]; ok && h.channel == chanCmd {
|
||||||
h.Close()
|
h.Close()
|
||||||
if hm, ok := c.hookcols[h.Key]; ok {
|
if hm, ok := c.hookcols[h.Key]; ok {
|
||||||
delete(hm, h.Name)
|
delete(hm, h.Name)
|
||||||
@ -205,7 +235,9 @@ func (c *Controller) cmdDelHook(msg *server.Message) (res resp.Value, d commandD
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) cmdPDelHook(msg *server.Message) (res resp.Value, d commandDetailsT, err error) {
|
func (c *Controller) cmdPDelHook(msg *server.Message, channel bool) (
|
||||||
|
res resp.Value, d commandDetailsT, err error,
|
||||||
|
) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
vs := msg.Values[1:]
|
vs := msg.Values[1:]
|
||||||
|
|
||||||
@ -219,19 +251,21 @@ func (c *Controller) cmdPDelHook(msg *server.Message) (res resp.Value, d command
|
|||||||
}
|
}
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
for name := range c.hooks {
|
for name, h := range c.hooks {
|
||||||
match, _ := glob.Match(pattern, name)
|
if h.channel != channel {
|
||||||
if match {
|
continue
|
||||||
if h, ok := c.hooks[name]; ok {
|
|
||||||
h.Close()
|
|
||||||
if hm, ok := c.hookcols[h.Key]; ok {
|
|
||||||
delete(hm, h.Name)
|
|
||||||
}
|
|
||||||
delete(c.hooks, h.Name)
|
|
||||||
d.updated = true
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
match, _ := glob.Match(pattern, name)
|
||||||
|
if !match {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h.Close()
|
||||||
|
if hm, ok := c.hookcols[h.Key]; ok {
|
||||||
|
delete(hm, h.Name)
|
||||||
|
}
|
||||||
|
delete(c.hooks, h.Name)
|
||||||
|
d.updated = true
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
d.timestamp = time.Now()
|
d.timestamp = time.Now()
|
||||||
|
|
||||||
@ -244,7 +278,9 @@ func (c *Controller) cmdPDelHook(msg *server.Message) (res resp.Value, d command
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) cmdHooks(msg *server.Message) (res resp.Value, err error) {
|
func (c *Controller) cmdHooks(msg *server.Message, channel bool) (
|
||||||
|
res resp.Value, err error,
|
||||||
|
) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
vs := msg.Values[1:]
|
vs := msg.Values[1:]
|
||||||
|
|
||||||
@ -260,6 +296,9 @@ func (c *Controller) cmdHooks(msg *server.Message) (res resp.Value, err error) {
|
|||||||
|
|
||||||
var hooks []*Hook
|
var hooks []*Hook
|
||||||
for name, hook := range c.hooks {
|
for name, hook := range c.hooks {
|
||||||
|
if hook.channel != channel {
|
||||||
|
continue
|
||||||
|
}
|
||||||
match, _ := glob.Match(pattern, name)
|
match, _ := glob.Match(pattern, name)
|
||||||
if match {
|
if match {
|
||||||
hooks = append(hooks, hook)
|
hooks = append(hooks, hook)
|
||||||
@ -270,7 +309,12 @@ func (c *Controller) cmdHooks(msg *server.Message) (res resp.Value, err error) {
|
|||||||
switch msg.OutputType {
|
switch msg.OutputType {
|
||||||
case server.JSON:
|
case server.JSON:
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
buf.WriteString(`{"ok":true,"hooks":[`)
|
buf.WriteString(`{"ok":true,`)
|
||||||
|
if channel {
|
||||||
|
buf.WriteString(`"chans":[`)
|
||||||
|
} else {
|
||||||
|
buf.WriteString(`"hooks":[`)
|
||||||
|
}
|
||||||
for i, hook := range hooks {
|
for i, hook := range hooks {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
buf.WriteByte(',')
|
buf.WriteByte(',')
|
||||||
@ -278,12 +322,14 @@ func (c *Controller) cmdHooks(msg *server.Message) (res resp.Value, err error) {
|
|||||||
buf.WriteString(`{`)
|
buf.WriteString(`{`)
|
||||||
buf.WriteString(`"name":` + jsonString(hook.Name))
|
buf.WriteString(`"name":` + jsonString(hook.Name))
|
||||||
buf.WriteString(`,"key":` + jsonString(hook.Key))
|
buf.WriteString(`,"key":` + jsonString(hook.Key))
|
||||||
buf.WriteString(`,"endpoints":[`)
|
if !channel {
|
||||||
for i, endpoint := range hook.Endpoints {
|
buf.WriteString(`,"endpoints":[`)
|
||||||
if i > 0 {
|
for i, endpoint := range hook.Endpoints {
|
||||||
buf.WriteByte(',')
|
if i > 0 {
|
||||||
|
buf.WriteByte(',')
|
||||||
|
}
|
||||||
|
buf.WriteString(jsonString(endpoint))
|
||||||
}
|
}
|
||||||
buf.WriteString(jsonString(endpoint))
|
|
||||||
}
|
}
|
||||||
buf.WriteString(`],"command":[`)
|
buf.WriteString(`],"command":[`)
|
||||||
for i, v := range hook.Message.Values {
|
for i, v := range hook.Message.Values {
|
||||||
@ -303,7 +349,8 @@ func (c *Controller) cmdHooks(msg *server.Message) (res resp.Value, err error) {
|
|||||||
}
|
}
|
||||||
buf.WriteString(`}}`)
|
buf.WriteString(`}}`)
|
||||||
}
|
}
|
||||||
buf.WriteString(`],"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
buf.WriteString(`],"elapsed":"` +
|
||||||
|
time.Now().Sub(start).String() + "\"}")
|
||||||
return resp.StringValue(buf.String()), nil
|
return resp.StringValue(buf.String()), nil
|
||||||
case server.RESP:
|
case server.RESP:
|
||||||
var vals []resp.Value
|
var vals []resp.Value
|
||||||
@ -332,7 +379,6 @@ func (c *Controller) cmdHooks(msg *server.Message) (res resp.Value, err error) {
|
|||||||
|
|
||||||
// Hook represents a hook.
|
// Hook represents a hook.
|
||||||
type Hook struct {
|
type Hook struct {
|
||||||
mu sync.Mutex
|
|
||||||
cond *sync.Cond
|
cond *sync.Cond
|
||||||
Key string
|
Key string
|
||||||
Name string
|
Name string
|
||||||
@ -342,12 +388,15 @@ type Hook struct {
|
|||||||
ScanWriter *scanWriter
|
ScanWriter *scanWriter
|
||||||
Metas []FenceMeta
|
Metas []FenceMeta
|
||||||
db *buntdb.DB
|
db *buntdb.DB
|
||||||
|
channel bool
|
||||||
closed bool
|
closed bool
|
||||||
opened bool
|
opened bool
|
||||||
query string
|
query string
|
||||||
epm *endpoint.Manager
|
epm *endpoint.Manager
|
||||||
|
expires time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equals returns true if two hooks are equal
|
||||||
func (h *Hook) Equals(hook *Hook) bool {
|
func (h *Hook) Equals(hook *Hook) bool {
|
||||||
if h.Key != hook.Key ||
|
if h.Key != hook.Key ||
|
||||||
h.Name != hook.Name ||
|
h.Name != hook.Name ||
|
||||||
@ -370,6 +419,7 @@ func (h *Hook) Equals(hook *Hook) bool {
|
|||||||
resp.ArrayValue(hook.Message.Values))
|
resp.ArrayValue(hook.Message.Values))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FenceMeta is a meta key/value pair for fences
|
||||||
type FenceMeta struct {
|
type FenceMeta struct {
|
||||||
Name, Value string
|
Name, Value string
|
||||||
}
|
}
|
||||||
@ -391,21 +441,28 @@ func (arr hookMetaByName) Swap(a, b int) {
|
|||||||
// Open is called when a hook is first created. It calls the manager
|
// Open is called when a hook is first created. It calls the manager
|
||||||
// function in a goroutine
|
// function in a goroutine
|
||||||
func (h *Hook) Open() {
|
func (h *Hook) Open() {
|
||||||
h.mu.Lock()
|
if h.channel {
|
||||||
defer h.mu.Unlock()
|
// nothing to open for channels
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.cond.L.Lock()
|
||||||
|
defer h.cond.L.Unlock()
|
||||||
if h.opened {
|
if h.opened {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.opened = true
|
h.opened = true
|
||||||
b, _ := json.Marshal(h.Name)
|
h.query = `{"hook":` + jsonString(h.Name) + `}`
|
||||||
h.query = `{"hook":` + string(b) + `}`
|
|
||||||
go h.manager()
|
go h.manager()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closed the hook and stop the manager function
|
// Close closed the hook and stop the manager function
|
||||||
func (h *Hook) Close() {
|
func (h *Hook) Close() {
|
||||||
h.mu.Lock()
|
if h.channel {
|
||||||
defer h.mu.Unlock()
|
// nothing to close for channels
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.cond.L.Lock()
|
||||||
|
defer h.cond.L.Unlock()
|
||||||
if h.closed {
|
if h.closed {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -416,30 +473,35 @@ func (h *Hook) Close() {
|
|||||||
// Signal can be called at any point to wake up the hook and
|
// Signal can be called at any point to wake up the hook and
|
||||||
// notify the manager that there may be something new in the queue.
|
// notify the manager that there may be something new in the queue.
|
||||||
func (h *Hook) Signal() {
|
func (h *Hook) Signal() {
|
||||||
h.mu.Lock()
|
if h.channel {
|
||||||
|
// nothing to signal for channels
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.cond.L.Lock()
|
||||||
h.cond.Broadcast()
|
h.cond.Broadcast()
|
||||||
h.mu.Unlock()
|
h.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// the manager is a forever loop that calls proc whenever there's a signal.
|
// the manager is a forever loop that calls proc whenever there's a signal.
|
||||||
// it ends when the "closed" flag is set.
|
// it ends when the "closed" flag is set.
|
||||||
func (h *Hook) manager() {
|
func (h *Hook) manager() {
|
||||||
for {
|
for {
|
||||||
h.mu.Lock()
|
h.cond.L.Lock()
|
||||||
for {
|
for {
|
||||||
if h.closed {
|
if h.closed {
|
||||||
h.mu.Unlock()
|
h.cond.L.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if h.proc() {
|
if h.proc() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
h.mu.Unlock()
|
h.cond.L.Unlock()
|
||||||
time.Sleep(time.Second / 4)
|
// proc failed. wait half a second and try again
|
||||||
h.mu.Lock()
|
time.Sleep(time.Second / 2)
|
||||||
|
h.cond.L.Lock()
|
||||||
}
|
}
|
||||||
h.cond.Wait()
|
h.cond.Wait()
|
||||||
h.mu.Unlock()
|
h.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,13 +514,15 @@ func (h *Hook) proc() (ok bool) {
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
err := h.db.Update(func(tx *buntdb.Tx) error {
|
err := h.db.Update(func(tx *buntdb.Tx) error {
|
||||||
// get keys and vals
|
// get keys and vals
|
||||||
err := tx.AscendGreaterOrEqual("hooks", h.query, func(key, val string) bool {
|
err := tx.AscendGreaterOrEqual("hooks",
|
||||||
if strings.HasPrefix(key, hookLogPrefix) {
|
h.query, func(key, val string) bool {
|
||||||
keys = append(keys, key)
|
if strings.HasPrefix(key, hookLogPrefix) {
|
||||||
vals = append(vals, val)
|
keys = append(keys, key)
|
||||||
}
|
vals = append(vals, val)
|
||||||
return true
|
}
|
||||||
})
|
return true
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -494,7 +558,8 @@ func (h *Hook) proc() (ok bool) {
|
|||||||
for _, endpoint := range h.Endpoints {
|
for _, endpoint := range h.Endpoints {
|
||||||
err := h.epm.Send(endpoint, val)
|
err := h.epm.Send(endpoint, val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Endpoint connect/send error: %v: %v: %v", idx, endpoint, err)
|
log.Debugf("Endpoint connect/send error: %v: %v: %v",
|
||||||
|
idx, endpoint, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Debugf("Endpoint send ok: %v: %v: %v", idx, endpoint, err)
|
log.Debugf("Endpoint send ok: %v: %v: %v", idx, endpoint, err)
|
||||||
@ -502,7 +567,8 @@ func (h *Hook) proc() (ok bool) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !sent {
|
if !sent {
|
||||||
// failed to send. try to reinsert the remaining. if this fails we lose log entries.
|
// failed to send. try to reinsert the remaining.
|
||||||
|
// if this fails we lose log entries.
|
||||||
keys = keys[i:]
|
keys = keys[i:]
|
||||||
vals = vals[i:]
|
vals = vals[i:]
|
||||||
ttls = ttls[i:]
|
ttls = ttls[i:]
|
||||||
@ -528,41 +594,3 @@ func (h *Hook) proc() (ok bool) {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// Do performs a hook.
|
|
||||||
func (hook *Hook) Do(details *commandDetailsT) error {
|
|
||||||
var lerrs []error
|
|
||||||
msgs := FenceMatch(hook.Name, hook.ScanWriter, hook.Fence, details)
|
|
||||||
nextMessage:
|
|
||||||
for _, msg := range msgs {
|
|
||||||
nextEndpoint:
|
|
||||||
for _, endpoint := range hook.Endpoints {
|
|
||||||
switch endpoint.Protocol {
|
|
||||||
case HTTP:
|
|
||||||
if err := sendHTTPMessage(endpoint, []byte(msg)); err != nil {
|
|
||||||
lerrs = append(lerrs, err)
|
|
||||||
continue nextEndpoint
|
|
||||||
}
|
|
||||||
continue nextMessage // sent
|
|
||||||
case Disque:
|
|
||||||
if err := sendDisqueMessage(endpoint, []byte(msg)); err != nil {
|
|
||||||
lerrs = append(lerrs, err)
|
|
||||||
continue nextEndpoint
|
|
||||||
}
|
|
||||||
continue nextMessage // sent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(lerrs) == 0 {
|
|
||||||
// log.Notice("YAY")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var errmsgs []string
|
|
||||||
for _, err := range lerrs {
|
|
||||||
errmsgs = append(errmsgs, err.Error())
|
|
||||||
}
|
|
||||||
err := errors.New("not sent: " + strings.Join(errmsgs, ","))
|
|
||||||
log.Error(err)
|
|
||||||
return err
|
|
||||||
}*/
|
|
||||||
|
@ -43,7 +43,13 @@ func (c *Controller) processLives() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeMessage(conn net.Conn, message []byte, wrapRESP bool, connType server.Type, websocket bool) error {
|
func writeLiveMessage(
|
||||||
|
conn net.Conn,
|
||||||
|
message []byte,
|
||||||
|
wrapRESP bool,
|
||||||
|
connType server.Type,
|
||||||
|
websocket bool,
|
||||||
|
) error {
|
||||||
if len(message) == 0 {
|
if len(message) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -70,28 +76,34 @@ func (c *Controller) goLive(inerr error, conn net.Conn, rd *server.PipelineReade
|
|||||||
defer func() {
|
defer func() {
|
||||||
log.Info("not live " + addr)
|
log.Info("not live " + addr)
|
||||||
}()
|
}()
|
||||||
if s, ok := inerr.(liveAOFSwitches); ok {
|
switch s := inerr.(type) {
|
||||||
|
default:
|
||||||
|
return errors.New("invalid live type switches")
|
||||||
|
case liveAOFSwitches:
|
||||||
return c.liveAOF(s.pos, conn, rd, msg)
|
return c.liveAOF(s.pos, conn, rd, msg)
|
||||||
|
case liveSubscriptionSwitches:
|
||||||
|
return c.liveSubscription(conn, rd, msg, websocket)
|
||||||
|
case liveFenceSwitches:
|
||||||
|
// fallthrough
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// everything below is for live geofences
|
||||||
lb := &liveBuffer{
|
lb := &liveBuffer{
|
||||||
cond: sync.NewCond(&sync.Mutex{}),
|
cond: sync.NewCond(&sync.Mutex{}),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
var sw *scanWriter
|
var sw *scanWriter
|
||||||
var wr bytes.Buffer
|
var wr bytes.Buffer
|
||||||
switch s := inerr.(type) {
|
s := inerr.(liveFenceSwitches)
|
||||||
default:
|
lb.glob = s.glob
|
||||||
return errors.New("invalid switch")
|
lb.key = s.key
|
||||||
case liveFenceSwitches:
|
lb.fence = &s
|
||||||
lb.glob = s.glob
|
c.mu.RLock()
|
||||||
lb.key = s.key
|
sw, err = c.newScanWriter(
|
||||||
lb.fence = &s
|
&wr, msg, s.key, s.output, s.precision, s.glob, false,
|
||||||
c.mu.RLock()
|
s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields)
|
||||||
sw, err = c.newScanWriter(
|
c.mu.RUnlock()
|
||||||
&wr, msg, s.key, s.output, s.precision, s.glob, false,
|
|
||||||
s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields)
|
|
||||||
c.mu.RUnlock()
|
|
||||||
}
|
|
||||||
// everything below if for live SCAN, NEARBY, WITHIN, INTERSECTS
|
// everything below if for live SCAN, NEARBY, WITHIN, INTERSECTS
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -149,7 +161,7 @@ func (c *Controller) goLive(inerr error, conn net.Conn, rd *server.PipelineReade
|
|||||||
case server.RESP:
|
case server.RESP:
|
||||||
livemsg = []byte("+OK\r\n")
|
livemsg = []byte("+OK\r\n")
|
||||||
}
|
}
|
||||||
if err := writeMessage(conn, livemsg, false, connType, websocket); err != nil {
|
if err := writeLiveMessage(conn, livemsg, false, connType, websocket); err != nil {
|
||||||
return nil // nil return is fine here
|
return nil // nil return is fine here
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
@ -168,7 +180,7 @@ func (c *Controller) goLive(inerr error, conn net.Conn, rd *server.PipelineReade
|
|||||||
lb.cond.L.Unlock()
|
lb.cond.L.Unlock()
|
||||||
msgs := FenceMatch("", sw, fence, nil, details)
|
msgs := FenceMatch("", sw, fence, nil, details)
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
if err := writeMessage(conn, []byte(msg), true, connType, websocket); err != nil {
|
if err := writeLiveMessage(conn, []byte(msg), true, connType, websocket); err != nil {
|
||||||
return nil // nil return is fine here
|
return nil // nil return is fine here
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
363
pkg/controller/pubsub.go
Normal file
363
pkg/controller/pubsub.go
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"github.com/tidwall/match"
|
||||||
|
"github.com/tidwall/redcon"
|
||||||
|
"github.com/tidwall/resp"
|
||||||
|
"github.com/tidwall/tile38/pkg/log"
|
||||||
|
"github.com/tidwall/tile38/pkg/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pubsubChannel = iota
|
||||||
|
pubsubPattern
|
||||||
|
)
|
||||||
|
|
||||||
|
type pubsub struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
hubs [2]map[string]*subhub
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPubsub() *pubsub {
|
||||||
|
return &pubsub{
|
||||||
|
hubs: [2]map[string]*subhub{
|
||||||
|
make(map[string]*subhub),
|
||||||
|
make(map[string]*subhub),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish a message to subscribers
|
||||||
|
func (c *Controller) Publish(channel string, message ...string) int {
|
||||||
|
var msgs []submsg
|
||||||
|
c.pubsub.mu.RLock()
|
||||||
|
if hub := c.pubsub.hubs[pubsubChannel][channel]; hub != nil {
|
||||||
|
for target := range hub.targets {
|
||||||
|
for _, message := range message {
|
||||||
|
msgs = append(msgs, submsg{
|
||||||
|
kind: pubsubChannel,
|
||||||
|
target: target,
|
||||||
|
channel: channel,
|
||||||
|
message: message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for pattern, hub := range c.pubsub.hubs[pubsubPattern] {
|
||||||
|
if match.Match(channel, pattern) {
|
||||||
|
for target := range hub.targets {
|
||||||
|
for _, message := range message {
|
||||||
|
msgs = append(msgs, submsg{
|
||||||
|
kind: pubsubPattern,
|
||||||
|
target: target,
|
||||||
|
channel: channel,
|
||||||
|
pattern: pattern,
|
||||||
|
message: message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.pubsub.mu.RUnlock()
|
||||||
|
|
||||||
|
for _, msg := range msgs {
|
||||||
|
msg.target.cond.L.Lock()
|
||||||
|
msg.target.msgs = append(msg.target.msgs, msg)
|
||||||
|
msg.target.cond.Broadcast()
|
||||||
|
msg.target.cond.L.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *pubsub) register(kind int, channel string, target *subtarget) {
|
||||||
|
ps.mu.Lock()
|
||||||
|
hub, ok := ps.hubs[kind][channel]
|
||||||
|
if !ok {
|
||||||
|
hub = newSubhub()
|
||||||
|
ps.hubs[kind][channel] = hub
|
||||||
|
}
|
||||||
|
hub.targets[target] = true
|
||||||
|
ps.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *pubsub) unregister(kind int, channel string, target *subtarget) {
|
||||||
|
ps.mu.Lock()
|
||||||
|
hub, ok := ps.hubs[kind][channel]
|
||||||
|
if ok {
|
||||||
|
delete(hub.targets, target)
|
||||||
|
if len(hub.targets) == 0 {
|
||||||
|
delete(ps.hubs[kind], channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ps.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
type submsg struct {
|
||||||
|
kind byte
|
||||||
|
target *subtarget
|
||||||
|
pattern string
|
||||||
|
channel string
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
type subtarget struct {
|
||||||
|
cond *sync.Cond
|
||||||
|
msgs []submsg
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSubtarget() *subtarget {
|
||||||
|
target := new(subtarget)
|
||||||
|
target.cond = sync.NewCond(&sync.Mutex{})
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
type subhub struct {
|
||||||
|
targets map[*subtarget]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSubhub() *subhub {
|
||||||
|
hub := new(subhub)
|
||||||
|
hub.targets = make(map[*subtarget]bool)
|
||||||
|
return hub
|
||||||
|
}
|
||||||
|
|
||||||
|
type liveSubscriptionSwitches struct {
|
||||||
|
// no fields. everything is managed through the server.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sub liveSubscriptionSwitches) Error() string {
|
||||||
|
return goingLive
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) cmdSubscribe(msg *server.Message) (resp.Value, error) {
|
||||||
|
if len(msg.Values) < 2 {
|
||||||
|
return resp.Value{}, errInvalidNumberOfArguments
|
||||||
|
}
|
||||||
|
return server.NOMessage, liveSubscriptionSwitches{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) cmdPsubscribe(msg *server.Message) (resp.Value, error) {
|
||||||
|
if len(msg.Values) < 2 {
|
||||||
|
return resp.Value{}, errInvalidNumberOfArguments
|
||||||
|
}
|
||||||
|
return server.NOMessage, liveSubscriptionSwitches{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) cmdPublish(msg *server.Message) (resp.Value, error) {
|
||||||
|
start := time.Now()
|
||||||
|
if len(msg.Values) != 3 {
|
||||||
|
return resp.Value{}, errInvalidNumberOfArguments
|
||||||
|
}
|
||||||
|
|
||||||
|
channel := msg.Values[1].String()
|
||||||
|
message := msg.Values[2].String()
|
||||||
|
//geofence := gjson.Valid(message) && gjson.Get(message, "fence").Bool()
|
||||||
|
n := c.Publish(channel, message) //, geofence)
|
||||||
|
var res resp.Value
|
||||||
|
switch msg.OutputType {
|
||||||
|
case server.JSON:
|
||||||
|
res = resp.StringValue(`{"ok":true` +
|
||||||
|
`,"published":` + strconv.FormatInt(int64(n), 10) +
|
||||||
|
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`)
|
||||||
|
case server.RESP:
|
||||||
|
res = resp.IntegerValue(n)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) liveSubscription(
|
||||||
|
conn net.Conn,
|
||||||
|
rd *server.PipelineReader,
|
||||||
|
msg *server.Message,
|
||||||
|
websocket bool,
|
||||||
|
) error {
|
||||||
|
defer conn.Close() // close connection when we are done
|
||||||
|
|
||||||
|
outputType := msg.OutputType
|
||||||
|
connType := msg.ConnType
|
||||||
|
if websocket {
|
||||||
|
outputType = server.JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
var start time.Time
|
||||||
|
|
||||||
|
// write helpers
|
||||||
|
var writeLock sync.Mutex
|
||||||
|
write := func(data []byte) {
|
||||||
|
writeLock.Lock()
|
||||||
|
defer writeLock.Unlock()
|
||||||
|
writeLiveMessage(conn, data, false, connType, websocket)
|
||||||
|
}
|
||||||
|
writeOK := func() {
|
||||||
|
switch outputType {
|
||||||
|
case server.JSON:
|
||||||
|
write([]byte(`{"ok":true` +
|
||||||
|
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
|
||||||
|
case server.RESP:
|
||||||
|
write([]byte(`+OK\r\n`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeWrongNumberOfArgsErr := func(command string) {
|
||||||
|
switch outputType {
|
||||||
|
case server.JSON:
|
||||||
|
write([]byte(`{"ok":false,"err":"invalid number of arguments"` +
|
||||||
|
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
|
||||||
|
case server.RESP:
|
||||||
|
write([]byte(`-ERR wrong number of arguments ` +
|
||||||
|
`for '` + command + `' command\r\n`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeOnlyPubsubErr := func() {
|
||||||
|
switch outputType {
|
||||||
|
case server.JSON:
|
||||||
|
write([]byte(`{"ok":false` +
|
||||||
|
`,"err":"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / ` +
|
||||||
|
`PING / QUIT allowed in this context"` +
|
||||||
|
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
|
||||||
|
case server.RESP:
|
||||||
|
write([]byte("-ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / " +
|
||||||
|
"PING / QUIT allowed in this context\r\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeSubscribe := func(command, channel string, num int) {
|
||||||
|
switch outputType {
|
||||||
|
case server.JSON:
|
||||||
|
write([]byte(`{"ok":true` +
|
||||||
|
`,"command":` + jsonString(command) +
|
||||||
|
`,"channel":` + jsonString(channel) +
|
||||||
|
`,"num":` + strconv.FormatInt(int64(num), 10) +
|
||||||
|
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
|
||||||
|
case server.RESP:
|
||||||
|
b := redcon.AppendArray(nil, 3)
|
||||||
|
b = redcon.AppendBulkString(b, command)
|
||||||
|
b = redcon.AppendBulkString(b, channel)
|
||||||
|
b = redcon.AppendInt(b, int64(num))
|
||||||
|
write(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeMessage := func(msg submsg) {
|
||||||
|
if msg.kind == pubsubChannel {
|
||||||
|
switch outputType {
|
||||||
|
case server.JSON:
|
||||||
|
var data []byte
|
||||||
|
if !gjson.Valid(msg.message) {
|
||||||
|
data = appendJSONString(nil, msg.message)
|
||||||
|
} else {
|
||||||
|
data = []byte(msg.message)
|
||||||
|
}
|
||||||
|
write(data)
|
||||||
|
case server.RESP:
|
||||||
|
b := redcon.AppendArray(nil, 3)
|
||||||
|
b = redcon.AppendBulkString(b, "message")
|
||||||
|
b = redcon.AppendBulkString(b, msg.channel)
|
||||||
|
b = redcon.AppendBulkString(b, msg.message)
|
||||||
|
write(b)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch outputType {
|
||||||
|
case server.JSON:
|
||||||
|
var data []byte
|
||||||
|
if !gjson.Valid(msg.message) {
|
||||||
|
data = appendJSONString(nil, msg.message)
|
||||||
|
} else {
|
||||||
|
data = []byte(msg.message)
|
||||||
|
}
|
||||||
|
write(data)
|
||||||
|
case server.RESP:
|
||||||
|
b := redcon.AppendArray(nil, 4)
|
||||||
|
b = redcon.AppendBulkString(b, "pmessage")
|
||||||
|
b = redcon.AppendBulkString(b, msg.pattern)
|
||||||
|
b = redcon.AppendBulkString(b, msg.channel)
|
||||||
|
b = redcon.AppendBulkString(b, msg.message)
|
||||||
|
write(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := [2]map[string]bool{
|
||||||
|
make(map[string]bool),
|
||||||
|
make(map[string]bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
target := newSubtarget()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
for channel := range m[i] {
|
||||||
|
c.pubsub.unregister(i, channel, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.cond.L.Lock()
|
||||||
|
target.closed = true
|
||||||
|
target.cond.Broadcast()
|
||||||
|
target.cond.L.Unlock()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
log.Debugf("pubsub open")
|
||||||
|
defer log.Debugf("pubsub closed")
|
||||||
|
for {
|
||||||
|
var msgs []submsg
|
||||||
|
target.cond.L.Lock()
|
||||||
|
if len(target.msgs) > 0 {
|
||||||
|
msgs = target.msgs
|
||||||
|
target.msgs = nil
|
||||||
|
}
|
||||||
|
target.cond.L.Unlock()
|
||||||
|
for _, msg := range msgs {
|
||||||
|
writeMessage(msg)
|
||||||
|
}
|
||||||
|
target.cond.L.Lock()
|
||||||
|
if target.closed {
|
||||||
|
target.cond.L.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target.cond.Wait()
|
||||||
|
target.cond.L.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
msgs := []*server.Message{msg}
|
||||||
|
for {
|
||||||
|
for _, msg := range msgs {
|
||||||
|
start = time.Now()
|
||||||
|
var kind int
|
||||||
|
switch msg.Command {
|
||||||
|
case "quit":
|
||||||
|
writeOK()
|
||||||
|
return nil
|
||||||
|
case "psubscribe":
|
||||||
|
kind = pubsubPattern
|
||||||
|
case "subscribe":
|
||||||
|
kind = pubsubChannel
|
||||||
|
default:
|
||||||
|
writeOnlyPubsubErr()
|
||||||
|
}
|
||||||
|
if len(msg.Values) < 2 {
|
||||||
|
writeWrongNumberOfArgsErr(msg.Command)
|
||||||
|
}
|
||||||
|
for i := 1; i < len(msg.Values); i++ {
|
||||||
|
channel := msg.Values[i].String()
|
||||||
|
m[kind][channel] = true
|
||||||
|
c.pubsub.register(kind, channel, target)
|
||||||
|
writeSubscribe(msg.Command, channel, len(m[0])+len(m[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
msgs, err = rd.ReadMessages()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,10 +11,15 @@ import (
|
|||||||
"github.com/tidwall/tile38/pkg/server"
|
"github.com/tidwall/tile38/pkg/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Controller) cmdScanArgs(vs []resp.Value) (s liveFenceSwitches, err error) {
|
func (c *Controller) cmdScanArgs(vs []resp.Value) (
|
||||||
if vs, s.searchScanBaseTokens, err = c.parseSearchScanBaseTokens("scan", vs); err != nil {
|
s liveFenceSwitches, err error,
|
||||||
|
) {
|
||||||
|
var t searchScanBaseTokens
|
||||||
|
vs, t, err = c.parseSearchScanBaseTokens("scan", t, vs)
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
s.searchScanBaseTokens = t
|
||||||
if len(vs) != 0 {
|
if len(vs) != 0 {
|
||||||
err = errInvalidNumberOfArguments
|
err = errInvalidNumberOfArguments
|
||||||
return
|
return
|
||||||
|
@ -38,7 +38,7 @@ type roamSwitches struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s liveFenceSwitches) Error() string {
|
func (s liveFenceSwitches) Error() string {
|
||||||
return "going live"
|
return goingLive
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s liveFenceSwitches) Close() {
|
func (s liveFenceSwitches) Close() {
|
||||||
@ -51,10 +51,18 @@ func (s liveFenceSwitches) usingLua() bool {
|
|||||||
return len(s.whereevals) > 0
|
return len(s.whereevals) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) cmdSearchArgs(cmd string, vs []resp.Value, types []string) (s liveFenceSwitches, err error) {
|
func (c *Controller) cmdSearchArgs(
|
||||||
if vs, s.searchScanBaseTokens, err = c.parseSearchScanBaseTokens(cmd, vs); err != nil {
|
fromFenceCmd bool, cmd string, vs []resp.Value, types []string,
|
||||||
|
) (s liveFenceSwitches, err error) {
|
||||||
|
var t searchScanBaseTokens
|
||||||
|
if fromFenceCmd {
|
||||||
|
t.fence = true
|
||||||
|
}
|
||||||
|
vs, t, err = c.parseSearchScanBaseTokens(cmd, t, vs)
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
s.searchScanBaseTokens = t
|
||||||
var typ string
|
var typ string
|
||||||
var ok bool
|
var ok bool
|
||||||
if vs, typ, ok = tokenval(vs); !ok || typ == "" {
|
if vs, typ, ok = tokenval(vs); !ok || typ == "" {
|
||||||
@ -350,7 +358,7 @@ func (c *Controller) cmdNearby(msg *server.Message) (res resp.Value, err error)
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
vs := msg.Values[1:]
|
vs := msg.Values[1:]
|
||||||
wr := &bytes.Buffer{}
|
wr := &bytes.Buffer{}
|
||||||
s, err := c.cmdSearchArgs("nearby", vs, nearbyTypes)
|
s, err := c.cmdSearchArgs(false, "nearby", vs, nearbyTypes)
|
||||||
if s.usingLua() {
|
if s.usingLua() {
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -473,7 +481,7 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
|
|||||||
vs := msg.Values[1:]
|
vs := msg.Values[1:]
|
||||||
|
|
||||||
wr := &bytes.Buffer{}
|
wr := &bytes.Buffer{}
|
||||||
s, err := c.cmdSearchArgs(cmd, vs, withinOrIntersectsTypes)
|
s, err := c.cmdSearchArgs(false, cmd, vs, withinOrIntersectsTypes)
|
||||||
if s.usingLua() {
|
if s.usingLua() {
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -533,11 +541,11 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return sw.writeObject(ScanWriterParams{
|
return sw.writeObject(ScanWriterParams{
|
||||||
id: id,
|
id: id,
|
||||||
o: o,
|
o: o,
|
||||||
fields: fields,
|
fields: fields,
|
||||||
noLock: true,
|
noLock: true,
|
||||||
clip: s.clip,
|
clip: s.clip,
|
||||||
clipbox: clipbox,
|
clipbox: clipbox,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -552,10 +560,15 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
|
|||||||
return sw.respOut, nil
|
return sw.respOut, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) cmdSeachValuesArgs(vs []resp.Value) (s liveFenceSwitches, err error) {
|
func (c *Controller) cmdSeachValuesArgs(vs []resp.Value) (
|
||||||
if vs, s.searchScanBaseTokens, err = c.parseSearchScanBaseTokens("search", vs); err != nil {
|
s liveFenceSwitches, err error,
|
||||||
|
) {
|
||||||
|
var t searchScanBaseTokens
|
||||||
|
vs, t, err = c.parseSearchScanBaseTokens("search", t, vs)
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
s.searchScanBaseTokens = t
|
||||||
if len(vs) != 0 {
|
if len(vs) != 0 {
|
||||||
err = errInvalidNumberOfArguments
|
err = errInvalidNumberOfArguments
|
||||||
return
|
return
|
||||||
|
@ -168,15 +168,15 @@ func (wherein whereinT) match(value float64) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type whereevalT struct {
|
type whereevalT struct {
|
||||||
c *Controller
|
c *Controller
|
||||||
luaState *lua.LState
|
luaState *lua.LState
|
||||||
fn *lua.LFunction
|
fn *lua.LFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
func (whereeval whereevalT) Close() {
|
func (whereeval whereevalT) Close() {
|
||||||
luaSetRawGlobals(
|
luaSetRawGlobals(
|
||||||
whereeval.luaState, map[string]lua.LValue{
|
whereeval.luaState, map[string]lua.LValue{
|
||||||
"ARGV": lua.LNil,
|
"ARGV": lua.LNil,
|
||||||
})
|
})
|
||||||
whereeval.c.luapool.Put(whereeval.luaState)
|
whereeval.c.luapool.Put(whereeval.luaState)
|
||||||
}
|
}
|
||||||
@ -189,11 +189,11 @@ func (whereeval whereevalT) match(fieldsWithNames map[string]float64) bool {
|
|||||||
|
|
||||||
luaSetRawGlobals(
|
luaSetRawGlobals(
|
||||||
whereeval.luaState, map[string]lua.LValue{
|
whereeval.luaState, map[string]lua.LValue{
|
||||||
"FIELDS": fieldsTbl,
|
"FIELDS": fieldsTbl,
|
||||||
})
|
})
|
||||||
defer luaSetRawGlobals(
|
defer luaSetRawGlobals(
|
||||||
whereeval.luaState, map[string]lua.LValue{
|
whereeval.luaState, map[string]lua.LValue{
|
||||||
"FIELDS": lua.LNil,
|
"FIELDS": lua.LNil,
|
||||||
})
|
})
|
||||||
|
|
||||||
whereeval.luaState.Push(whereeval.fn)
|
whereeval.luaState.Push(whereeval.fn)
|
||||||
@ -219,41 +219,48 @@ func (whereeval whereevalT) match(fieldsWithNames map[string]float64) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
var match bool
|
var match bool
|
||||||
tbl.ForEach(func(lk lua.LValue, lv lua.LValue) {match = true})
|
tbl.ForEach(func(lk lua.LValue, lv lua.LValue) { match = true })
|
||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
panic(fmt.Sprintf("Script returned value of type %s", ret.Type()))
|
panic(fmt.Sprintf("Script returned value of type %s", ret.Type()))
|
||||||
}
|
}
|
||||||
|
|
||||||
type searchScanBaseTokens struct {
|
type searchScanBaseTokens struct {
|
||||||
key string
|
key string
|
||||||
cursor uint64
|
cursor uint64
|
||||||
output outputT
|
output outputT
|
||||||
precision uint64
|
precision uint64
|
||||||
lineout string
|
lineout string
|
||||||
fence bool
|
fence bool
|
||||||
distance bool
|
distance bool
|
||||||
detect map[string]bool
|
detect map[string]bool
|
||||||
accept map[string]bool
|
accept map[string]bool
|
||||||
glob string
|
glob string
|
||||||
wheres []whereT
|
wheres []whereT
|
||||||
whereins []whereinT
|
whereins []whereinT
|
||||||
whereevals []whereevalT
|
whereevals []whereevalT
|
||||||
nofields bool
|
nofields bool
|
||||||
ulimit bool
|
ulimit bool
|
||||||
limit uint64
|
limit uint64
|
||||||
usparse bool
|
usparse bool
|
||||||
sparse uint8
|
sparse uint8
|
||||||
desc bool
|
desc bool
|
||||||
clip bool
|
clip bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vsout []resp.Value, t searchScanBaseTokens, err error) {
|
func (c *Controller) parseSearchScanBaseTokens(
|
||||||
|
cmd string, t searchScanBaseTokens, vs []resp.Value,
|
||||||
|
) (
|
||||||
|
vsout []resp.Value, tout searchScanBaseTokens, err error,
|
||||||
|
) {
|
||||||
var ok bool
|
var ok bool
|
||||||
if vs, t.key, ok = tokenval(vs); !ok || t.key == "" {
|
if vs, t.key, ok = tokenval(vs); !ok || t.key == "" {
|
||||||
err = errInvalidNumberOfArguments
|
err = errInvalidNumberOfArguments
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fromFence := t.fence
|
||||||
|
|
||||||
var slimit string
|
var slimit string
|
||||||
var ssparse string
|
var ssparse string
|
||||||
var scursor string
|
var scursor string
|
||||||
@ -261,7 +268,8 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
for {
|
for {
|
||||||
nvs, wtok, ok := tokenval(vs)
|
nvs, wtok, ok := tokenval(vs)
|
||||||
if ok && len(wtok) > 0 {
|
if ok && len(wtok) > 0 {
|
||||||
if (wtok[0] == 'C' || wtok[0] == 'c') && strings.ToLower(wtok) == "cursor" {
|
switch strings.ToLower(wtok) {
|
||||||
|
case "cursor":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if scursor != "" {
|
if scursor != "" {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
@ -272,7 +280,7 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'W' || wtok[0] == 'w') && strings.ToLower(wtok) == "where" {
|
case "where":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
var field, smin, smax string
|
var field, smin, smax string
|
||||||
if vs, field, ok = tokenval(vs); !ok || field == "" {
|
if vs, field, ok = tokenval(vs); !ok || field == "" {
|
||||||
@ -317,7 +325,7 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
}
|
}
|
||||||
t.wheres = append(t.wheres, whereT{field, minx, min, maxx, max})
|
t.wheres = append(t.wheres, whereT{field, minx, min, maxx, max})
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'W' || wtok[0] == 'w') && strings.ToLower(wtok) == "wherein" {
|
case "wherein":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
var field, nvalsStr, valStr string
|
var field, nvalsStr, valStr string
|
||||||
if vs, field, ok = tokenval(vs); !ok || field == "" {
|
if vs, field, ok = tokenval(vs); !ok || field == "" {
|
||||||
@ -349,7 +357,9 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
}
|
}
|
||||||
t.whereins = append(t.whereins, whereinT{field, valMap})
|
t.whereins = append(t.whereins, whereinT{field, valMap})
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'W' || wtok[0] == 'w') && strings.Contains(strings.ToLower(wtok), "whereeval") {
|
case "whereevalsha":
|
||||||
|
fallthrough
|
||||||
|
case "whereeval":
|
||||||
scriptIsSha := strings.ToLower(wtok) == "whereevalsha"
|
scriptIsSha := strings.ToLower(wtok) == "whereevalsha"
|
||||||
vs = nvs
|
vs = nvs
|
||||||
var script, nargsStr, arg string
|
var script, nargsStr, arg string
|
||||||
@ -392,7 +402,7 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
|
|
||||||
luaSetRawGlobals(
|
luaSetRawGlobals(
|
||||||
luaState, map[string]lua.LValue{
|
luaState, map[string]lua.LValue{
|
||||||
"ARGV": argsTbl,
|
"ARGV": argsTbl,
|
||||||
})
|
})
|
||||||
|
|
||||||
compiled, ok := c.luascripts.Get(shaSum)
|
compiled, ok := c.luascripts.Get(shaSum)
|
||||||
@ -417,9 +427,9 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
}
|
}
|
||||||
c.luascripts.Put(shaSum, fn.Proto)
|
c.luascripts.Put(shaSum, fn.Proto)
|
||||||
}
|
}
|
||||||
t.whereevals = append(t.whereevals, whereevalT{c,luaState, fn})
|
t.whereevals = append(t.whereevals, whereevalT{c, luaState, fn})
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'N' || wtok[0] == 'n') && strings.ToLower(wtok) == "nofields" {
|
case "nofields":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if t.nofields {
|
if t.nofields {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
@ -427,7 +437,7 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
}
|
}
|
||||||
t.nofields = true
|
t.nofields = true
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'L' || wtok[0] == 'l') && strings.ToLower(wtok) == "limit" {
|
case "limit":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if slimit != "" {
|
if slimit != "" {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
@ -438,7 +448,7 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'S' || wtok[0] == 's') && strings.ToLower(wtok) == "sparse" {
|
case "sparse":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if ssparse != "" {
|
if ssparse != "" {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
@ -449,15 +459,15 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'F' || wtok[0] == 'f') && strings.ToLower(wtok) == "fence" {
|
case "fence":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if t.fence {
|
if t.fence && !fromFence {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.fence = true
|
t.fence = true
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'C' || wtok[0] == 'c') && strings.ToLower(wtok) == "commands" {
|
case "commands":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if t.accept != nil {
|
if t.accept != nil {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
@ -481,7 +491,7 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
t.accept = nil
|
t.accept = nil
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'D' || wtok[0] == 'd') && strings.ToLower(wtok) == "distance" {
|
case "distance":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if t.distance {
|
if t.distance {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
@ -489,7 +499,7 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
}
|
}
|
||||||
t.distance = true
|
t.distance = true
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'D' || wtok[0] == 'd') && strings.ToLower(wtok) == "detect" {
|
case "detect":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if t.detect != nil {
|
if t.detect != nil {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
@ -525,7 +535,7 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'D' || wtok[0] == 'd') && strings.ToLower(wtok) == "desc" {
|
case "desc":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if t.desc || asc {
|
if t.desc || asc {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
@ -533,7 +543,7 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
}
|
}
|
||||||
t.desc = true
|
t.desc = true
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'A' || wtok[0] == 'a') && strings.ToLower(wtok) == "asc" {
|
case "asc":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if t.desc || asc {
|
if t.desc || asc {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
@ -541,7 +551,7 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
}
|
}
|
||||||
asc = true
|
asc = true
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'M' || wtok[0] == 'm') && strings.ToLower(wtok) == "match" {
|
case "match":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if t.glob != "" {
|
if t.glob != "" {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
@ -552,7 +562,7 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
} else if (wtok[0] == 'C' || wtok[0] == 'c') && strings.ToLower(wtok) == "clip" {
|
case "clip":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if t.clip {
|
if t.clip {
|
||||||
err = errDuplicateArgument(strings.ToUpper(wtok))
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
@ -666,5 +676,6 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
|||||||
t.limit = math.MaxUint64
|
t.limit = math.MaxUint64
|
||||||
}
|
}
|
||||||
vsout = vs
|
vsout = vs
|
||||||
|
tout = t
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
package endpoint
|
package endpoint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/garyburd/redigo/redis"
|
||||||
|
"github.com/tidwall/tile38/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -21,8 +19,7 @@ type DisqueConn struct {
|
|||||||
ep Endpoint
|
ep Endpoint
|
||||||
ex bool
|
ex bool
|
||||||
t time.Time
|
t time.Time
|
||||||
conn net.Conn
|
conn redis.Conn
|
||||||
rd *bufio.Reader
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDisqueConn(ep Endpoint) *DisqueConn {
|
func newDisqueConn(ep Endpoint) *DisqueConn {
|
||||||
@ -52,7 +49,6 @@ func (conn *DisqueConn) close() {
|
|||||||
conn.conn.Close()
|
conn.conn.Close()
|
||||||
conn.conn = nil
|
conn.conn = nil
|
||||||
}
|
}
|
||||||
conn.rd = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send sends a message
|
// Send sends a message
|
||||||
@ -66,60 +62,23 @@ func (conn *DisqueConn) Send(msg string) error {
|
|||||||
if conn.conn == nil {
|
if conn.conn == nil {
|
||||||
addr := fmt.Sprintf("%s:%d", conn.ep.Disque.Host, conn.ep.Disque.Port)
|
addr := fmt.Sprintf("%s:%d", conn.ep.Disque.Host, conn.ep.Disque.Port)
|
||||||
var err error
|
var err error
|
||||||
conn.conn, err = net.Dial("tcp", addr)
|
conn.conn, err = redis.Dial("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
conn.rd = bufio.NewReader(conn.conn)
|
|
||||||
}
|
}
|
||||||
var args []string
|
|
||||||
args = append(args, "ADDJOB", conn.ep.Disque.QueueName, msg, "0")
|
var args []interface{}
|
||||||
|
args = append(args, conn.ep.Disque.QueueName, msg, 0)
|
||||||
if conn.ep.Disque.Options.Replicate > 0 {
|
if conn.ep.Disque.Options.Replicate > 0 {
|
||||||
args = append(args, "REPLICATE", strconv.FormatInt(int64(conn.ep.Disque.Options.Replicate), 10))
|
args = append(args, "REPLICATE", conn.ep.Disque.Options.Replicate)
|
||||||
}
|
}
|
||||||
cmd := buildRedisCommand(args)
|
|
||||||
if _, err := conn.conn.Write(cmd); err != nil {
|
reply, err := redis.String(conn.conn.Do("ADDJOB", args...))
|
||||||
conn.close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c, err := conn.rd.ReadByte()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.close()
|
conn.close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c != '-' && c != '+' {
|
log.Debugf("Disque: ADDJOB '%s'", reply)
|
||||||
conn.close()
|
|
||||||
return errors.New("invalid disque reply")
|
|
||||||
}
|
|
||||||
ln, err := conn.rd.ReadBytes('\n')
|
|
||||||
if err != nil {
|
|
||||||
conn.close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(ln) < 2 || ln[len(ln)-2] != '\r' {
|
|
||||||
conn.close()
|
|
||||||
return errors.New("invalid disque reply")
|
|
||||||
}
|
|
||||||
id := string(ln[:len(ln)-2])
|
|
||||||
p := strings.Split(id, "-")
|
|
||||||
if len(p) != 4 {
|
|
||||||
conn.close()
|
|
||||||
return errors.New("invalid disque reply")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildRedisCommand(args []string) []byte {
|
|
||||||
var cmd []byte
|
|
||||||
cmd = append(cmd, '*')
|
|
||||||
cmd = strconv.AppendInt(cmd, int64(len(args)), 10)
|
|
||||||
cmd = append(cmd, '\r', '\n')
|
|
||||||
for _, arg := range args {
|
|
||||||
cmd = append(cmd, '$')
|
|
||||||
cmd = strconv.AppendInt(cmd, int64(len(arg)), 10)
|
|
||||||
cmd = append(cmd, '\r', '\n')
|
|
||||||
cmd = append(cmd, arg...)
|
|
||||||
cmd = append(cmd, '\r', '\n')
|
|
||||||
}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
@ -17,6 +17,8 @@ var errExpired = errors.New("expired")
|
|||||||
type Protocol string
|
type Protocol string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// Local protocol
|
||||||
|
Local = Protocol("local")
|
||||||
// HTTP protocol
|
// HTTP protocol
|
||||||
HTTP = Protocol("http")
|
HTTP = Protocol("http")
|
||||||
// Disque protocol
|
// Disque protocol
|
||||||
@ -87,7 +89,6 @@ type Endpoint struct {
|
|||||||
CertFile string
|
CertFile string
|
||||||
KeyFile string
|
KeyFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
SQS struct {
|
SQS struct {
|
||||||
QueueID string
|
QueueID string
|
||||||
Region string
|
Region string
|
||||||
@ -95,7 +96,6 @@ type Endpoint struct {
|
|||||||
CredProfile string
|
CredProfile string
|
||||||
QueueName string
|
QueueName string
|
||||||
}
|
}
|
||||||
|
|
||||||
NATS struct {
|
NATS struct {
|
||||||
Host string
|
Host string
|
||||||
Port int
|
Port int
|
||||||
@ -103,6 +103,9 @@ type Endpoint struct {
|
|||||||
Pass string
|
Pass string
|
||||||
Topic string
|
Topic string
|
||||||
}
|
}
|
||||||
|
Local struct {
|
||||||
|
Channel string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conn is an endpoint connection
|
// Conn is an endpoint connection
|
||||||
@ -113,14 +116,16 @@ type Conn interface {
|
|||||||
|
|
||||||
// Manager manages all endpoints
|
// Manager manages all endpoints
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
conns map[string]Conn
|
conns map[string]Conn
|
||||||
|
publisher LocalPublisher
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager returns a new manager
|
// NewManager returns a new manager
|
||||||
func NewManager() *Manager {
|
func NewManager(publisher LocalPublisher) *Manager {
|
||||||
epc := &Manager{
|
epc := &Manager{
|
||||||
conns: make(map[string]Conn),
|
conns: make(map[string]Conn),
|
||||||
|
publisher: publisher,
|
||||||
}
|
}
|
||||||
go epc.Run()
|
go epc.Run()
|
||||||
return epc
|
return epc
|
||||||
@ -180,6 +185,8 @@ func (epc *Manager) Send(endpoint, msg string) error {
|
|||||||
conn = newSQSConn(ep)
|
conn = newSQSConn(ep)
|
||||||
case NATS:
|
case NATS:
|
||||||
conn = newNATSConn(ep)
|
conn = newNATSConn(ep)
|
||||||
|
case Local:
|
||||||
|
conn = newLocalConn(ep, epc.publisher)
|
||||||
}
|
}
|
||||||
epc.conns[endpoint] = conn
|
epc.conns[endpoint] = conn
|
||||||
}
|
}
|
||||||
@ -204,6 +211,8 @@ func parseEndpoint(s string) (Endpoint, error) {
|
|||||||
switch {
|
switch {
|
||||||
default:
|
default:
|
||||||
return endpoint, errors.New("unknown scheme")
|
return endpoint, errors.New("unknown scheme")
|
||||||
|
case strings.HasPrefix(s, "local:"):
|
||||||
|
endpoint.Protocol = Local
|
||||||
case strings.HasPrefix(s, "http:"):
|
case strings.HasPrefix(s, "http:"):
|
||||||
endpoint.Protocol = HTTP
|
endpoint.Protocol = HTTP
|
||||||
case strings.HasPrefix(s, "https:"):
|
case strings.HasPrefix(s, "https:"):
|
||||||
@ -237,9 +246,17 @@ func parseEndpoint(s string) (Endpoint, error) {
|
|||||||
sp := strings.Split(sqp[0], "/")
|
sp := strings.Split(sqp[0], "/")
|
||||||
s = sp[0]
|
s = sp[0]
|
||||||
if s == "" {
|
if s == "" {
|
||||||
|
if endpoint.Protocol == Local {
|
||||||
|
return endpoint, errors.New("missing channel")
|
||||||
|
}
|
||||||
return endpoint, errors.New("missing host")
|
return endpoint, errors.New("missing host")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Local PubSub channel
|
||||||
|
// local://<channel>
|
||||||
|
if endpoint.Protocol == Local {
|
||||||
|
endpoint.Local.Channel = s
|
||||||
|
}
|
||||||
if endpoint.Protocol == GRPC {
|
if endpoint.Protocol == GRPC {
|
||||||
dp := strings.Split(s, ":")
|
dp := strings.Split(s, ":")
|
||||||
switch len(dp) {
|
switch len(dp) {
|
||||||
|
38
pkg/endpoint/local.go
Normal file
38
pkg/endpoint/local.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package endpoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
localExpiresAfter = time.Second * 30
|
||||||
|
)
|
||||||
|
|
||||||
|
// LocalPublisher is used to publish local notifcations
|
||||||
|
type LocalPublisher interface {
|
||||||
|
Publish(channel string, message ...string) int
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalConn is an endpoint connection
|
||||||
|
type LocalConn struct {
|
||||||
|
ep Endpoint
|
||||||
|
publisher LocalPublisher
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLocalConn(ep Endpoint, publisher LocalPublisher) *LocalConn {
|
||||||
|
return &LocalConn{
|
||||||
|
ep: ep,
|
||||||
|
publisher: publisher,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expired returns true if the connection has expired
|
||||||
|
func (conn *LocalConn) Expired() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends a message
|
||||||
|
func (conn *LocalConn) Send(msg string) error {
|
||||||
|
conn.publisher.Publish(conn.ep.Local.Channel, msg)
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,12 +1,11 @@
|
|||||||
package endpoint
|
package endpoint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/garyburd/redigo/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -19,8 +18,7 @@ type RedisConn struct {
|
|||||||
ep Endpoint
|
ep Endpoint
|
||||||
ex bool
|
ex bool
|
||||||
t time.Time
|
t time.Time
|
||||||
conn net.Conn
|
conn redis.Conn
|
||||||
rd *bufio.Reader
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRedisConn(ep Endpoint) *RedisConn {
|
func newRedisConn(ep Endpoint) *RedisConn {
|
||||||
@ -50,7 +48,6 @@ func (conn *RedisConn) close() {
|
|||||||
conn.conn.Close()
|
conn.conn.Close()
|
||||||
conn.conn = nil
|
conn.conn = nil
|
||||||
}
|
}
|
||||||
conn.rd = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send sends a message
|
// Send sends a message
|
||||||
@ -61,48 +58,20 @@ func (conn *RedisConn) Send(msg string) error {
|
|||||||
if conn.ex {
|
if conn.ex {
|
||||||
return errExpired
|
return errExpired
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.t = time.Now()
|
conn.t = time.Now()
|
||||||
if conn.conn == nil {
|
if conn.conn == nil {
|
||||||
addr := fmt.Sprintf("%s:%d", conn.ep.Redis.Host, conn.ep.Redis.Port)
|
addr := fmt.Sprintf("%s:%d", conn.ep.Redis.Host, conn.ep.Redis.Port)
|
||||||
var err error
|
var err error
|
||||||
conn.conn, err = net.Dial("tcp", addr)
|
conn.conn, err = redis.Dial("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
conn.close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
conn.rd = bufio.NewReader(conn.conn)
|
|
||||||
}
|
}
|
||||||
|
_, err := redis.Int(conn.conn.Do("PUBLISH", conn.ep.Redis.Channel, msg))
|
||||||
var args []string
|
|
||||||
args = append(args, "PUBLISH", conn.ep.Redis.Channel, msg)
|
|
||||||
cmd := buildRedisCommand(args)
|
|
||||||
|
|
||||||
if _, err := conn.conn.Write(cmd); err != nil {
|
|
||||||
conn.close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := conn.rd.ReadByte()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.close()
|
conn.close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c != ':' {
|
|
||||||
conn.close()
|
|
||||||
return errors.New("invalid redis reply")
|
|
||||||
}
|
|
||||||
|
|
||||||
ln, err := conn.rd.ReadBytes('\n')
|
|
||||||
if err != nil {
|
|
||||||
conn.close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(ln[0:1]) != "1" {
|
|
||||||
conn.close()
|
|
||||||
return errors.New("invalid redis reply")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,6 @@ func readNextHTTPCommand(packet []byte, argsIn [][]byte, msg *Message, wr io.Wri
|
|||||||
accept := base64.StdEncoding.EncodeToString(sum[:])
|
accept := base64.StdEncoding.EncodeToString(sum[:])
|
||||||
wshead := "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " + accept + "\r\n\r\n"
|
wshead := "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " + accept + "\r\n\r\n"
|
||||||
if _, err = wr.Write([]byte(wshead)); err != nil {
|
if _, err = wr.Write([]byte(wshead)); err != nil {
|
||||||
println(4)
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
} else if contentLength > 0 {
|
} else if contentLength > 0 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user