mirror of
https://github.com/m1k1o/neko-rooms.git
synced 2026-11-02 22:51:34 -06:00
move prefix handler to pkg.
This commit is contained in:
@@ -16,6 +16,8 @@ import (
|
|||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/m1k1o/neko-rooms/pkg/prefix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type entry struct {
|
type entry struct {
|
||||||
@@ -41,7 +43,7 @@ type ProxyManagerCtx struct {
|
|||||||
|
|
||||||
client *dockerClient.Client
|
client *dockerClient.Client
|
||||||
instanceName string
|
instanceName string
|
||||||
handlers *prefixHandler[*entry]
|
handlers prefix.Tree[*entry]
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(client *dockerClient.Client, instanceName string, waitEnabled bool) *ProxyManagerCtx {
|
func New(client *dockerClient.Client, instanceName string, waitEnabled bool) *ProxyManagerCtx {
|
||||||
@@ -52,7 +54,7 @@ func New(client *dockerClient.Client, instanceName string, waitEnabled bool) *Pr
|
|||||||
client: client,
|
client: client,
|
||||||
instanceName: instanceName,
|
instanceName: instanceName,
|
||||||
waitEnabled: waitEnabled,
|
waitEnabled: waitEnabled,
|
||||||
handlers: &prefixHandler[*entry]{},
|
handlers: prefix.NewTree[*entry](),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +121,7 @@ func (p *ProxyManagerCtx) Start() {
|
|||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
switch msg.Action {
|
switch msg.Action {
|
||||||
case "create":
|
case "create":
|
||||||
p.handlers.Set(path, &entry{
|
p.handlers.Insert(path, &entry{
|
||||||
id: msg.ID,
|
id: msg.ID,
|
||||||
running: false,
|
running: false,
|
||||||
})
|
})
|
||||||
@@ -137,9 +139,9 @@ func (p *ProxyManagerCtx) Start() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
p.handlers.Set(path, e)
|
p.handlers.Insert(path, e)
|
||||||
case "stop":
|
case "stop":
|
||||||
p.handlers.Set(path, &entry{
|
p.handlers.Insert(path, &entry{
|
||||||
id: msg.ID,
|
id: msg.ID,
|
||||||
running: false,
|
running: false,
|
||||||
})
|
})
|
||||||
@@ -172,7 +174,7 @@ func (p *ProxyManagerCtx) Refresh() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.handlers = &prefixHandler[*entry]{}
|
p.handlers = prefix.NewTree[*entry]()
|
||||||
|
|
||||||
for _, cont := range containers {
|
for _, cont := range containers {
|
||||||
enabled, path, port, ok := p.parseLabels(cont.Labels)
|
enabled, path, port, ok := p.parseLabels(cont.Labels)
|
||||||
@@ -198,7 +200,7 @@ func (p *ProxyManagerCtx) Refresh() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.handlers.Set(path, entry)
|
p.handlers.Insert(path, entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/*
|
||||||
|
Package prefix implements a tree data structure that can be used to store
|
||||||
|
and retrieve values based on a path prefix. It can be used to match paths
|
||||||
|
against a set of prefixes.
|
||||||
|
*/
|
||||||
|
package prefix
|
||||||
@@ -1,22 +1,29 @@
|
|||||||
package proxy
|
package prefix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type prefixHandler[T any] struct {
|
type Tree[T any] interface {
|
||||||
|
Insert(prefix string, value T)
|
||||||
|
Find(prefix string) (value T, ok bool)
|
||||||
|
Remove(prefix string)
|
||||||
|
Match(path string) (value T, prefix string, ok bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tree[T any] struct {
|
||||||
Value T
|
Value T
|
||||||
IsLeaf bool
|
IsLeaf bool
|
||||||
Children map[string]*prefixHandler[T]
|
Children map[string]*tree[T]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPrefixHandler[T any]() *prefixHandler[T] {
|
func NewTree[T any]() *tree[T] {
|
||||||
return &prefixHandler[T]{}
|
return &tree[T]{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *prefixHandler[T]) Set(prefix string, value T) {
|
func (p *tree[T]) Insert(prefix string, value T) {
|
||||||
arr := strings.Split(prefix, "/")
|
arr := strings.Split(prefix, "/")
|
||||||
len := len(arr)
|
l := len(arr)
|
||||||
|
|
||||||
for i, a := range arr {
|
for i, a := range arr {
|
||||||
if a == "" {
|
if a == "" {
|
||||||
@@ -24,15 +31,15 @@ func (p *prefixHandler[T]) Set(prefix string, value T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if p.Children == nil {
|
if p.Children == nil {
|
||||||
p.Children = map[string]*prefixHandler[T]{}
|
p.Children = map[string]*tree[T]{}
|
||||||
}
|
}
|
||||||
|
|
||||||
dat, ok := p.Children[a]
|
dat, ok := p.Children[a]
|
||||||
if !ok {
|
if !ok {
|
||||||
dat = &prefixHandler[T]{}
|
dat = &tree[T]{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if i == len-1 {
|
if i == l-1 {
|
||||||
dat.Value = value
|
dat.Value = value
|
||||||
dat.IsLeaf = true
|
dat.IsLeaf = true
|
||||||
dat.Children = nil
|
dat.Children = nil
|
||||||
@@ -43,9 +50,9 @@ func (p *prefixHandler[T]) Set(prefix string, value T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *prefixHandler[T]) Get(prefix string) (value T, ok bool) {
|
func (p *tree[T]) Find(prefix string) (value T, ok bool) {
|
||||||
arr := strings.Split(prefix, "/")
|
arr := strings.Split(prefix, "/")
|
||||||
len := len(arr)
|
l := len(arr)
|
||||||
|
|
||||||
for i, a := range arr {
|
for i, a := range arr {
|
||||||
if a == "" {
|
if a == "" {
|
||||||
@@ -53,15 +60,15 @@ func (p *prefixHandler[T]) Get(prefix string) (value T, ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if p.Children == nil {
|
if p.Children == nil {
|
||||||
p.Children = map[string]*prefixHandler[T]{}
|
p.Children = map[string]*tree[T]{}
|
||||||
}
|
}
|
||||||
|
|
||||||
dat, found := p.Children[a]
|
dat, found := p.Children[a]
|
||||||
if !found {
|
if !found {
|
||||||
dat = &prefixHandler[T]{}
|
dat = &tree[T]{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if i == len-1 {
|
if i == l-1 {
|
||||||
value = dat.Value
|
value = dat.Value
|
||||||
ok = dat.IsLeaf
|
ok = dat.IsLeaf
|
||||||
return
|
return
|
||||||
@@ -74,16 +81,17 @@ func (p *prefixHandler[T]) Get(prefix string) (value T, ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *prefixHandler[T]) Remove(prefix string) {
|
func (p *tree[T]) Remove(prefix string) {
|
||||||
arr := strings.Split(prefix, "/")
|
arr := strings.Split(prefix, "/")
|
||||||
len := len(arr)
|
l := len(arr)
|
||||||
|
|
||||||
|
ptrs := []*tree[T]{p}
|
||||||
for i, a := range arr {
|
for i, a := range arr {
|
||||||
if a == "" {
|
if a == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if i == len-1 {
|
if i == l-1 {
|
||||||
delete(p.Children, a)
|
delete(p.Children, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,12 +100,23 @@ func (p *prefixHandler[T]) Remove(prefix string) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
p = dat
|
p = dat
|
||||||
|
ptrs = append(ptrs, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove all empty references.
|
// remove all empty references
|
||||||
|
rm := false
|
||||||
|
for i := len(ptrs) - 1; i >= 0; i-- {
|
||||||
|
if len(ptrs[i].Children) == 0 {
|
||||||
|
rm = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rm {
|
||||||
|
ptrs[i].Children = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *prefixHandler[T]) Match(path string) (value T, prefix string, ok bool) {
|
func (p *tree[T]) Match(path string) (value T, prefix string, ok bool) {
|
||||||
arr := strings.Split(path, "/")
|
arr := strings.Split(path, "/")
|
||||||
prefixArr := []string{}
|
prefixArr := []string{}
|
||||||
|
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package prefix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMatch(t *testing.T) {
|
||||||
|
// create a new prefix tree
|
||||||
|
tree := NewTree[string]()
|
||||||
|
|
||||||
|
// add some values to the tree
|
||||||
|
tree.Insert("/users/1", "user1")
|
||||||
|
tree.Insert("/users/2", "user2")
|
||||||
|
|
||||||
|
// test matching a path that exists in the tree
|
||||||
|
value, prefix, ok := tree.Match("/users/1")
|
||||||
|
if !ok || value != "user1" || prefix != "/users/1" {
|
||||||
|
t.Errorf("Match failed for /users/1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test matching a path that exists in the tree but has a trailing slash
|
||||||
|
value, prefix, ok = tree.Match("/users/2/")
|
||||||
|
if !ok || value != "user2" || prefix != "/users/2" {
|
||||||
|
t.Errorf("Match failed for /users/2/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test matching a path that does not exist in the tree
|
||||||
|
_, _, ok = tree.Match("/users/4")
|
||||||
|
if ok {
|
||||||
|
t.Errorf("Match should have failed for /users/4")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test matching a path that partially exists in the tree
|
||||||
|
value, prefix, ok = tree.Match("/users/1/posts")
|
||||||
|
if !ok || value != "user1" || prefix != "/users/1" {
|
||||||
|
t.Errorf("Match failed for /users/1/posts: %v %v %v", value, prefix, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFind(t *testing.T) {
|
||||||
|
// create a new prefix tree
|
||||||
|
tree := NewTree[string]()
|
||||||
|
|
||||||
|
// add some values to the tree
|
||||||
|
tree.Insert("/users/1", "user1")
|
||||||
|
tree.Insert("/users/2", "user2")
|
||||||
|
|
||||||
|
// test finding a path that exists in the tree
|
||||||
|
value, ok := tree.Find("/users/1")
|
||||||
|
if !ok || value != "user1" {
|
||||||
|
t.Errorf("Find failed for /users/1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test finding a path that exists in the tree but has a trailing slash
|
||||||
|
_, ok = tree.Find("/users/2/")
|
||||||
|
if ok {
|
||||||
|
t.Errorf("Find should have failed for /users/2/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test finding a path that does not exist in the tree
|
||||||
|
_, ok = tree.Find("/users/4")
|
||||||
|
if ok {
|
||||||
|
t.Errorf("Find should have failed for /users/4")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test finding a path that partially exists in the tree
|
||||||
|
_, ok = tree.Find("/users/1/posts")
|
||||||
|
if ok {
|
||||||
|
t.Errorf("Find should have failed for /users/1/posts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemove(t *testing.T) {
|
||||||
|
// create a new prefix tree
|
||||||
|
tree := &tree[string]{}
|
||||||
|
|
||||||
|
// add some values to the tree
|
||||||
|
tree.Insert("/users/1", "user1")
|
||||||
|
tree.Insert("/users/2", "user2")
|
||||||
|
|
||||||
|
// test matching a path that exists in the tree
|
||||||
|
value, prefix, ok := tree.Match("/users/1")
|
||||||
|
if !ok || value != "user1" || prefix != "/users/1" {
|
||||||
|
t.Errorf("Match failed for /users/1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove a path that exists in the tree
|
||||||
|
tree.Remove("/users/1")
|
||||||
|
|
||||||
|
// test matching a path that exists in the tree
|
||||||
|
_, _, ok = tree.Match("/users/1")
|
||||||
|
if ok {
|
||||||
|
t.Errorf("Match should have failed for /users/1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test finding a path that exists in the tree
|
||||||
|
_, ok = tree.Find("/users/1")
|
||||||
|
if ok {
|
||||||
|
t.Errorf("Find should have failed for /users/1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove another path that exists in the tree
|
||||||
|
tree.Remove("/users/2")
|
||||||
|
|
||||||
|
// test matching a path that exists in the tree
|
||||||
|
_, _, ok = tree.Match("/users/2")
|
||||||
|
if ok {
|
||||||
|
t.Errorf("Match should have failed for /users/2")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the tree is empty
|
||||||
|
if tree.Children != nil {
|
||||||
|
t.Errorf("Tree should be empty, but contains %d children", len(tree.Children))
|
||||||
|
for k, v := range tree.Children {
|
||||||
|
t.Errorf("%s: %v", k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark the match function
|
||||||
|
func Benchmark(b *testing.B) {
|
||||||
|
// create a new prefix tree
|
||||||
|
tree := NewTree[string]()
|
||||||
|
|
||||||
|
// insert 1 - 10 levels
|
||||||
|
level := "/users"
|
||||||
|
for i := 1; i <= 10; i++ {
|
||||||
|
level += fmt.Sprintf("/%d", i)
|
||||||
|
tree.Insert(level, fmt.Sprintf("level_%d", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the benchmark
|
||||||
|
level = "/users"
|
||||||
|
for i := 0; i <= 10; i++ {
|
||||||
|
level += fmt.Sprintf("/%d", i)
|
||||||
|
b.Run(fmt.Sprintf("level_%d", i), func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
tree.Match(level)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user