[GH-ISSUE #81] Allow multiple client for the same host #40

Open
opened 2026-05-05 10:58:27 -06:00 by gitea-mirror · 0 comments
Owner

Originally created by @pims on GitHub (May 31, 2018).
Original GitHub issue: https://github.com/mmatczuk/go-http-tunnel/issues/81

What are your thoughts on allowing multiple clients to start a tunnel for the same host?

The use case I have in mind, is running the same application in two different environments (AWS, DigitalOcean) and randomly route to one or the other. Of course, we could add smarter load balancing functionality, but wanted to hear if this was something you’d consider.

Here’s what needs to change for a very quick and dirty example:

diff --git a/registry.go b/registry.go
index 98fead8..9b1f94d 100644
--- a/registry.go
+++ b/registry.go
@@ -6,8 +6,10 @@ package tunnel
 
 import (
 	"fmt"
+	"math/rand"
 	"net"
 	"sync"
+	"time"
 
 	"github.com/mmatczuk/go-http-tunnel/id"
 	"github.com/mmatczuk/go-http-tunnel/log"
@@ -33,7 +35,7 @@ type hostInfo struct {
 
 type registry struct {
 	items  map[id.ID]*RegistryItem
-	hosts  map[string]*hostInfo
+	hosts  map[string]map[id.ID]*hostInfo
 	mu     sync.RWMutex
 	logger log.Logger
 }
@@ -45,7 +47,7 @@ func newRegistry(logger log.Logger) *registry {
 
 	return &registry{
 		items:  make(map[id.ID]*RegistryItem),
-		hosts:  make(map[string]*hostInfo),
+		hosts:  make(map[string]map[id.ID]*hostInfo),
 		logger: logger,
 	}
 }
@@ -87,8 +89,20 @@ func (r *registry) Subscriber(hostPort string) (id.ID, *Auth, bool) {
 	if !ok {
 		return id.ID{}, nil, false
 	}
+	if len(h) == 0 {
+		return id.ID{}, nil, false
+	}
+
+	keys := []id.ID{}
+	for k := range h {
+		keys = append(keys, k)
+	}
+
+	rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
+	n := rnd.Intn(len(h))
+	key := keys[n]
 
-	return h.identifier, h.auth, ok
+	return h[key].identifier, h[key].auth, ok
 }
 
 // Unsubscribe removes client from registry and returns it's RegistryItem.
@@ -141,16 +155,18 @@ func (r *registry) set(i *RegistryItem, identifier id.ID) error {
 			if h.Auth != nil && h.Auth.User == "" {
 				return fmt.Errorf("missing auth user")
 			}
-			if _, ok := r.hosts[trimPort(h.Host)]; ok {
-				return fmt.Errorf("host %q is occupied", h.Host)
-			}
 		}
 
 		for _, h := range i.Hosts {
-			r.hosts[trimPort(h.Host)] = &hostInfo{
+			hosts, found := r.hosts[trimPort(h.Host)]
+			if !found {
+				hosts = make(map[id.ID]*hostInfo)
+			}
+			hosts[identifier] = &hostInfo{
 				identifier: identifier,
 				auth:       h.Auth,
 			}
+			r.hosts[trimPort(h.Host)] = hosts
 		}
 	}
 
@@ -176,7 +192,14 @@ func (r *registry) clear(identifier id.ID) *RegistryItem {
 
 	if i.Hosts != nil {
 		for _, h := range i.Hosts {
-			delete(r.hosts, trimPort(h.Host))
+			peers := r.hosts[trimPort(h.Host)]
+			if len(peers) == 1 {
+				delete(r.hosts, trimPort(h.Host))
+			} else {
+				delete(peers, identifier)
+				r.hosts[trimPort(h.Host)] = peers
+			}
+
 		}
 	}
 
Originally created by @pims on GitHub (May 31, 2018). Original GitHub issue: https://github.com/mmatczuk/go-http-tunnel/issues/81 What are your thoughts on allowing multiple clients to start a tunnel for the same *host*? The use case I have in mind, is running the same application in two different environments (AWS, DigitalOcean) and randomly route to one or the other. Of course, we could add smarter load balancing functionality, but wanted to hear if this was something you’d consider. Here’s what needs to change for a very quick and dirty example: ```diff diff --git a/registry.go b/registry.go index 98fead8..9b1f94d 100644 --- a/registry.go +++ b/registry.go @@ -6,8 +6,10 @@ package tunnel import ( "fmt" + "math/rand" "net" "sync" + "time" "github.com/mmatczuk/go-http-tunnel/id" "github.com/mmatczuk/go-http-tunnel/log" @@ -33,7 +35,7 @@ type hostInfo struct { type registry struct { items map[id.ID]*RegistryItem - hosts map[string]*hostInfo + hosts map[string]map[id.ID]*hostInfo mu sync.RWMutex logger log.Logger } @@ -45,7 +47,7 @@ func newRegistry(logger log.Logger) *registry { return &registry{ items: make(map[id.ID]*RegistryItem), - hosts: make(map[string]*hostInfo), + hosts: make(map[string]map[id.ID]*hostInfo), logger: logger, } } @@ -87,8 +89,20 @@ func (r *registry) Subscriber(hostPort string) (id.ID, *Auth, bool) { if !ok { return id.ID{}, nil, false } + if len(h) == 0 { + return id.ID{}, nil, false + } + + keys := []id.ID{} + for k := range h { + keys = append(keys, k) + } + + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) + n := rnd.Intn(len(h)) + key := keys[n] - return h.identifier, h.auth, ok + return h[key].identifier, h[key].auth, ok } // Unsubscribe removes client from registry and returns it's RegistryItem. @@ -141,16 +155,18 @@ func (r *registry) set(i *RegistryItem, identifier id.ID) error { if h.Auth != nil && h.Auth.User == "" { return fmt.Errorf("missing auth user") } - if _, ok := r.hosts[trimPort(h.Host)]; ok { - return fmt.Errorf("host %q is occupied", h.Host) - } } for _, h := range i.Hosts { - r.hosts[trimPort(h.Host)] = &hostInfo{ + hosts, found := r.hosts[trimPort(h.Host)] + if !found { + hosts = make(map[id.ID]*hostInfo) + } + hosts[identifier] = &hostInfo{ identifier: identifier, auth: h.Auth, } + r.hosts[trimPort(h.Host)] = hosts } } @@ -176,7 +192,14 @@ func (r *registry) clear(identifier id.ID) *RegistryItem { if i.Hosts != nil { for _, h := range i.Hosts { - delete(r.hosts, trimPort(h.Host)) + peers := r.hosts[trimPort(h.Host)] + if len(peers) == 1 { + delete(r.hosts, trimPort(h.Host)) + } else { + delete(peers, identifier) + r.hosts[trimPort(h.Host)] = peers + } + } } ```
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/go-http-tunnel#40
No description provided.