mirror of https://github.com/tidwall/tile38.git
Metadata for Webhooks
Added the `META name value` keyword to the SETHOOK command. Allows for adding metadata to a webhook. For example: SETHOOK myhook http://endpoint/ META m1 12 META m2 13 NEARBY ... Would result in notification that contain the "meta" element, which is represented like: "meta":{"m1":"12","m2":"13"} Thanks for the suggestion @amorskoy closed #105
This commit is contained in:
parent
73fd3cf7de
commit
bafb1823b3
|
@ -32,7 +32,7 @@ var (
|
||||||
quiet bool
|
quiet bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fire up a webhook test server by using the --webhook-consumer-http-port
|
// Fire up a webhook test server by using the --webhook-http-consumer-port
|
||||||
// for example
|
// for example
|
||||||
// $ ./tile38-server --webhook-http-consumer-port 9999
|
// $ ./tile38-server --webhook-http-consumer-port 9999
|
||||||
//
|
//
|
||||||
|
|
|
@ -229,7 +229,7 @@ func (c *Controller) queueHooks(d *commandDetailsT) error {
|
||||||
if hm, ok := c.hookcols[d.key]; ok {
|
if hm, ok := c.hookcols[d.key]; ok {
|
||||||
for _, hook := range hm {
|
for _, hook := range hm {
|
||||||
// match the fence
|
// match the fence
|
||||||
msgs := FenceMatch(hook.Name, hook.ScanWriter, hook.Fence, 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
|
// append each msg to the big list
|
||||||
hmsgs = append(hmsgs, msgs...)
|
hmsgs = append(hmsgs, msgs...)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
@ -13,13 +12,8 @@ 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, details *commandDetailsT) [][]byte {
|
func FenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas []FenceMeta, details *commandDetailsT) [][]byte {
|
||||||
overall := time.Now()
|
msgs := fenceMatch(hookName, sw, fence, metas, details)
|
||||||
defer func() {
|
|
||||||
return
|
|
||||||
fmt.Printf(">> %v\n", time.Since(overall))
|
|
||||||
}()
|
|
||||||
msgs := fenceMatch(hookName, sw, fence, details)
|
|
||||||
if len(fence.accept) == 0 {
|
if len(fence.accept) == 0 {
|
||||||
return msgs
|
return msgs
|
||||||
}
|
}
|
||||||
|
@ -42,10 +36,31 @@ func jsonTimeFormat(t time.Time) string {
|
||||||
b = appendJSONTimeFormat(b, t)
|
b = appendJSONTimeFormat(b, t)
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
func appendHookDetails(b []byte, hookName string, metas []FenceMeta) []byte {
|
||||||
func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, details *commandDetailsT) [][]byte {
|
if len(hookName) > 0 {
|
||||||
|
b = append(b, `,"hook":`...)
|
||||||
|
b = appendJSONString(b, hookName)
|
||||||
|
}
|
||||||
|
if len(metas) > 0 {
|
||||||
|
b = append(b, `,"meta":{`...)
|
||||||
|
for i, meta := range metas {
|
||||||
|
if i > 0 {
|
||||||
|
b = append(b, ',')
|
||||||
|
}
|
||||||
|
b = appendJSONString(b, meta.Name)
|
||||||
|
b = append(b, ':')
|
||||||
|
b = appendJSONString(b, meta.Value)
|
||||||
|
}
|
||||||
|
b = append(b, '}')
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
func hookJSONString(hookName string, metas []FenceMeta) string {
|
||||||
|
return string(appendHookDetails(nil, hookName, metas))
|
||||||
|
}
|
||||||
|
func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas []FenceMeta, details *commandDetailsT) [][]byte {
|
||||||
if details.command == "drop" {
|
if details.command == "drop" {
|
||||||
return [][]byte{[]byte(`{"command":"drop","hook":` + jsonString(hookName) + `,"time":` + jsonTimeFormat(details.timestamp) + `}`)}
|
return [][]byte{[]byte(`{"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)
|
||||||
|
@ -65,7 +80,7 @@ func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, detai
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if details.command == "del" {
|
if details.command == "del" {
|
||||||
return [][]byte{[]byte(`{"command":"del","hook":` + jsonString(hookName) + `,"id":` + jsonString(details.id) + `,"time":` + jsonTimeFormat(details.timestamp) + `}`)}
|
return [][]byte{[]byte(`{"command":"del"` + hookJSONString(hookName, metas) + `,"id":` + jsonString(details.id) + `,"time":` + jsonTimeFormat(details.timestamp) + `}`)}
|
||||||
}
|
}
|
||||||
|
|
||||||
var roamkeys, roamids []string
|
var roamkeys, roamids []string
|
||||||
|
@ -174,18 +189,18 @@ func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, detai
|
||||||
msgs := make([][]byte, 0, 4)
|
msgs := make([][]byte, 0, 4)
|
||||||
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] == '{' {
|
||||||
res = makemsg(details.command, group, detect, hookName, details.key, details.timestamp, res[1:])
|
res = makemsg(details.command, group, detect, hookName, metas, details.key, details.timestamp, res[1:])
|
||||||
}
|
}
|
||||||
msgs = append(msgs, res)
|
msgs = append(msgs, res)
|
||||||
}
|
}
|
||||||
switch detect {
|
switch detect {
|
||||||
case "enter":
|
case "enter":
|
||||||
if fence.detect == nil || fence.detect["inside"] {
|
if fence.detect == nil || fence.detect["inside"] {
|
||||||
msgs = append(msgs, makemsg(details.command, group, "inside", hookName, details.key, details.timestamp, res[1:]))
|
msgs = append(msgs, makemsg(details.command, group, "inside", hookName, metas, details.key, details.timestamp, res[1:]))
|
||||||
}
|
}
|
||||||
case "exit", "cross":
|
case "exit", "cross":
|
||||||
if fence.detect == nil || fence.detect["outside"] {
|
if fence.detect == nil || fence.detect["outside"] {
|
||||||
msgs = append(msgs, makemsg(details.command, group, "outside", hookName, details.key, details.timestamp, res[1:]))
|
msgs = append(msgs, makemsg(details.command, group, "outside", hookName, metas, details.key, details.timestamp, res[1:]))
|
||||||
}
|
}
|
||||||
case "roam":
|
case "roam":
|
||||||
if len(msgs) > 0 {
|
if len(msgs) > 0 {
|
||||||
|
@ -244,12 +259,13 @@ func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, detai
|
||||||
return msgs
|
return msgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func makemsg(command, group, detect, hookName string, key string, t time.Time, tail []byte) []byte {
|
func makemsg(command, group, detect, hookName string, metas []FenceMeta, key string, t time.Time, tail []byte) []byte {
|
||||||
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...)
|
||||||
buf = append(append(buf, `","detect":"`...), detect...)
|
buf = append(append(buf, `","detect":"`...), detect...)
|
||||||
buf = appendJSONString(append(buf, `","hook":`...), hookName)
|
buf = append(buf, '"')
|
||||||
|
buf = appendHookDetails(buf, hookName, metas)
|
||||||
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...)
|
||||||
|
|
|
@ -66,21 +66,36 @@ func (c *Controller) cmdSetHook(msg *server.Message) (res string, d commandDetai
|
||||||
}
|
}
|
||||||
endpoints = append(endpoints, url)
|
endpoints = append(endpoints, url)
|
||||||
}
|
}
|
||||||
|
var commandvs []resp.Value
|
||||||
commandvs := vs
|
var cmdlc string
|
||||||
if vs, cmd, ok = tokenval(vs); !ok || cmd == "" {
|
|
||||||
return "", d, errInvalidNumberOfArguments
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdlc := strings.ToLower(cmd)
|
|
||||||
var types []string
|
var types []string
|
||||||
switch cmdlc {
|
metaMap := make(map[string]string)
|
||||||
default:
|
for {
|
||||||
return "", d, errInvalidArgument(cmd)
|
commandvs = vs
|
||||||
case "nearby":
|
if vs, cmd, ok = tokenval(vs); !ok || cmd == "" {
|
||||||
types = nearbyTypes
|
return "", d, errInvalidNumberOfArguments
|
||||||
case "within", "intersects":
|
}
|
||||||
types = withinOrIntersectsTypes
|
cmdlc = strings.ToLower(cmd)
|
||||||
|
switch cmdlc {
|
||||||
|
default:
|
||||||
|
return "", d, errInvalidArgument(cmd)
|
||||||
|
case "meta":
|
||||||
|
var metakey string
|
||||||
|
var metaval string
|
||||||
|
if vs, metakey, ok = tokenval(vs); !ok || metakey == "" {
|
||||||
|
return "", d, errInvalidNumberOfArguments
|
||||||
|
}
|
||||||
|
if vs, metaval, ok = tokenval(vs); !ok || metaval == "" {
|
||||||
|
return "", d, errInvalidNumberOfArguments
|
||||||
|
}
|
||||||
|
metaMap[metakey] = metaval
|
||||||
|
continue
|
||||||
|
case "nearby":
|
||||||
|
types = nearbyTypes
|
||||||
|
case "within", "intersects":
|
||||||
|
types = withinOrIntersectsTypes
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
s, err := c.cmdSearchArgs(cmdlc, vs, types)
|
s, err := c.cmdSearchArgs(cmdlc, vs, types)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -99,6 +114,12 @@ func (c *Controller) cmdSetHook(msg *server.Message) (res string, d commandDetai
|
||||||
}
|
}
|
||||||
cmsg.Command = strings.ToLower(cmsg.Values[0].String())
|
cmsg.Command = strings.ToLower(cmsg.Values[0].String())
|
||||||
|
|
||||||
|
metas := make([]FenceMeta, 0, len(metaMap))
|
||||||
|
for key, val := range metaMap {
|
||||||
|
metas = append(metas, FenceMeta{key, val})
|
||||||
|
}
|
||||||
|
sort.Sort(hookMetaByName(metas))
|
||||||
|
|
||||||
hook := &Hook{
|
hook := &Hook{
|
||||||
Key: s.key,
|
Key: s.key,
|
||||||
Name: name,
|
Name: name,
|
||||||
|
@ -107,6 +128,7 @@ func (c *Controller) cmdSetHook(msg *server.Message) (res string, d commandDetai
|
||||||
Message: cmsg,
|
Message: cmsg,
|
||||||
db: c.qdb,
|
db: c.qdb,
|
||||||
epm: c.epc,
|
epm: c.epc,
|
||||||
|
Metas: metas,
|
||||||
}
|
}
|
||||||
hook.cond = sync.NewCond(&hook.mu)
|
hook.cond = sync.NewCond(&hook.mu)
|
||||||
|
|
||||||
|
@ -117,27 +139,15 @@ func (c *Controller) cmdSetHook(msg *server.Message) (res string, d commandDetai
|
||||||
}
|
}
|
||||||
|
|
||||||
if h, ok := c.hooks[name]; ok {
|
if h, ok := c.hooks[name]; ok {
|
||||||
// lets see if the previous hook matches the new hook
|
if h.Equals(hook) {
|
||||||
if h.Key == hook.Key && h.Name == hook.Name {
|
// it was a match so we do nothing. But let's signal just
|
||||||
if len(h.Endpoints) == len(hook.Endpoints) {
|
// for good measure.
|
||||||
match := true
|
h.Signal()
|
||||||
for i, endpoint := range h.Endpoints {
|
switch msg.OutputType {
|
||||||
if endpoint != hook.Endpoints[i] {
|
case server.JSON:
|
||||||
match = false
|
return server.OKMessage(msg, start), d, nil
|
||||||
break
|
case server.RESP:
|
||||||
}
|
return ":0\r\n", d, nil
|
||||||
}
|
|
||||||
if match && resp.ArrayValue(h.Message.Values).Equals(resp.ArrayValue(hook.Message.Values)) {
|
|
||||||
// it was a match so we do nothing. But let's signal just
|
|
||||||
// for good measure.
|
|
||||||
h.Signal()
|
|
||||||
switch msg.OutputType {
|
|
||||||
case server.JSON:
|
|
||||||
return server.OKMessage(msg, start), d, nil
|
|
||||||
case server.RESP:
|
|
||||||
return ":0\r\n", d, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.Close()
|
h.Close()
|
||||||
|
@ -323,6 +333,7 @@ type Hook struct {
|
||||||
Message *server.Message
|
Message *server.Message
|
||||||
Fence *liveFenceSwitches
|
Fence *liveFenceSwitches
|
||||||
ScanWriter *scanWriter
|
ScanWriter *scanWriter
|
||||||
|
Metas []FenceMeta
|
||||||
db *buntdb.DB
|
db *buntdb.DB
|
||||||
closed bool
|
closed bool
|
||||||
opened bool
|
opened bool
|
||||||
|
@ -330,6 +341,46 @@ type Hook struct {
|
||||||
epm *endpoint.EndpointManager
|
epm *endpoint.EndpointManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Hook) Equals(hook *Hook) bool {
|
||||||
|
if h.Key != hook.Key ||
|
||||||
|
h.Name != hook.Name ||
|
||||||
|
len(h.Endpoints) != len(hook.Endpoints) ||
|
||||||
|
len(h.Metas) != len(hook.Metas) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, endpoint := range h.Endpoints {
|
||||||
|
if endpoint != hook.Endpoints[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, meta := range h.Metas {
|
||||||
|
if meta.Name != hook.Metas[i].Name ||
|
||||||
|
meta.Value != hook.Metas[i].Value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp.ArrayValue(h.Message.Values).Equals(
|
||||||
|
resp.ArrayValue(hook.Message.Values))
|
||||||
|
}
|
||||||
|
|
||||||
|
type FenceMeta struct {
|
||||||
|
Name, Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type hookMetaByName []FenceMeta
|
||||||
|
|
||||||
|
func (arr hookMetaByName) Len() int {
|
||||||
|
return len(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (arr hookMetaByName) Less(a, b int) bool {
|
||||||
|
return arr[a].Name < arr[b].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (arr hookMetaByName) Swap(a, b int) {
|
||||||
|
arr[a], arr[b] = arr[b], arr[a]
|
||||||
|
}
|
||||||
|
|
||||||
// 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() {
|
||||||
|
|
|
@ -162,7 +162,7 @@ func (c *Controller) goLive(inerr error, conn net.Conn, rd *server.AnyReaderWrit
|
||||||
}
|
}
|
||||||
fence := lb.fence
|
fence := lb.fence
|
||||||
lb.cond.L.Unlock()
|
lb.cond.L.Unlock()
|
||||||
msgs := FenceMatch("", sw, fence, 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 := writeMessage(conn, []byte(msg), true, connType, websocket); err != nil {
|
||||||
return nil // nil return is fine here
|
return nil // nil return is fine here
|
||||||
|
|
|
@ -1104,6 +1104,13 @@
|
||||||
"name": "endpoint",
|
"name": "endpoint",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "META",
|
||||||
|
"name": ["name", "value"],
|
||||||
|
"type": ["string", "string"],
|
||||||
|
"optional": true,
|
||||||
|
"multiple": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"enum": ["NEARBY", "WITHIN", "INTERSECTS"]
|
"enum": ["NEARBY", "WITHIN", "INTERSECTS"]
|
||||||
},
|
},
|
||||||
|
|
|
@ -1266,6 +1266,13 @@ var commandsJSON = `{
|
||||||
"name": "endpoint",
|
"name": "endpoint",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "META",
|
||||||
|
"name": ["name", "value"],
|
||||||
|
"type": ["string", "string"],
|
||||||
|
"optional": true,
|
||||||
|
"multiple": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"enum": ["NEARBY", "WITHIN", "INTERSECTS"]
|
"enum": ["NEARBY", "WITHIN", "INTERSECTS"]
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue