refactor layout and structure

This commit is contained in:
Alisdair MacLeod
2026-01-21 13:52:22 +00:00
parent 2851e38a1f
commit 1d8390b935
51 changed files with 2298 additions and 4430 deletions

View File

@@ -0,0 +1,24 @@
package proxy
import (
"context"
)
type requestContextKey string
const (
serviceIdKey requestContextKey = "serviceId"
)
func withServiceId(ctx context.Context, serviceId string) context.Context {
return context.WithValue(ctx, serviceIdKey, serviceId)
}
func ServiceIdFromContext(ctx context.Context) string {
v := ctx.Value(serviceIdKey)
serviceId, ok := v.(string)
if !ok {
return ""
}
return serviceId
}

View File

@@ -0,0 +1,44 @@
package proxy
import (
"net/http"
"net/http/httputil"
"sync"
)
type ReverseProxy struct {
transport http.RoundTripper
mappingsMux sync.RWMutex
mappings map[string]Mapping
}
// NewReverseProxy configures a new NetBird ReverseProxy.
// This is a wrapper around an httputil.ReverseProxy set
// to dynamically route requests based on internal mapping
// between requested URLs and targets.
// The internal mappings can be modified using the AddMapping
// and RemoveMapping functions.
func NewReverseProxy(transport http.RoundTripper) *ReverseProxy {
return &ReverseProxy{
transport: transport,
mappings: make(map[string]Mapping),
}
}
func (p *ReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
target, serviceId, exists := p.findTargetForRequest(r)
if !exists {
// No mapping found so return an error here.
// TODO: prettier error page.
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
// Set the serviceId in the context for later retrieval.
ctx := withServiceId(r.Context(), serviceId)
// Set up a reverse proxy using the transport and then use it to serve the request.
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.Transport = p.transport
proxy.ServeHTTP(w, r.WithContext(ctx))
}

View File

@@ -0,0 +1,62 @@
package proxy
import (
"net/http"
"net/url"
"sort"
"strings"
)
type Mapping struct {
ID string
Host string
Paths map[string]*url.URL
}
func (p *ReverseProxy) findTargetForRequest(req *http.Request) (*url.URL, string, bool) {
p.mappingsMux.RLock()
if p.mappings == nil {
p.mappingsMux.RUnlock()
p.mappingsMux.Lock()
defer p.mappingsMux.Unlock()
p.mappings = make(map[string]Mapping)
// There cannot be any loaded Mappings as we have only just initialized.
return nil, "", false
}
defer p.mappingsMux.RUnlock()
m, exists := p.mappings[req.Host]
if !exists {
return nil, "", false
}
// Sort paths by length (longest first) in a naive attempt to match the most specific route first.
paths := make([]string, 0, len(m.Paths))
for path := range m.Paths {
paths = append(paths, path)
}
sort.Slice(paths, func(i, j int) bool {
return len(paths[i]) > len(paths[j])
})
for _, path := range paths {
if strings.HasPrefix(req.URL.Path, path) {
return m.Paths[path], m.ID, true
}
}
return nil, "", false
}
func (p *ReverseProxy) AddMapping(m Mapping) {
p.mappingsMux.Lock()
defer p.mappingsMux.Unlock()
if p.mappings == nil {
p.mappings = make(map[string]Mapping)
}
p.mappings[m.Host] = m
}
func (p *ReverseProxy) RemoveMapping(m Mapping) {
p.mappingsMux.Lock()
defer p.mappingsMux.Unlock()
delete(p.mappings, m.Host)
}