proxy store context in labels.

This commit is contained in:
Miroslav Šedivý
2022-08-01 23:34:26 +02:00
parent 6f35d8f2a3
commit 29727e307e
6 changed files with 145 additions and 81 deletions
+35 -65
View File
@@ -6,8 +6,6 @@ import (
"net/http"
"net/http/httputil"
"net/url"
"path"
"strings"
"sync"
dockerTypes "github.com/docker/docker/api/types"
@@ -15,8 +13,6 @@ import (
dockerClient "github.com/docker/docker/client"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/m1k1o/neko-rooms/internal/config"
)
type ProxyManagerCtx struct {
@@ -24,26 +20,19 @@ type ProxyManagerCtx struct {
mu sync.RWMutex
ctx context.Context
cancel func()
prefix string
client *dockerClient.Client
config *config.Room
handlers map[string]*httputil.ReverseProxy
instanceName string
handlers *prefixHandler
}
func New(client *dockerClient.Client, config *config.Room) *ProxyManagerCtx {
logger := log.With().Str("module", "proxy").Logger()
prefix := config.PathPrefix
if !strings.HasSuffix(prefix, "/") {
prefix += "/"
}
func New(client *dockerClient.Client, instanceName string) *ProxyManagerCtx {
return &ProxyManagerCtx{
logger: logger,
prefix: prefix,
logger: log.With().Str("module", "proxy").Logger(),
client: client,
config: config,
handlers: map[string]*httputil.ReverseProxy{},
instanceName: instanceName,
handlers: &prefixHandler{},
}
}
@@ -59,7 +48,8 @@ func (p *ProxyManagerCtx) Start() {
msgs, errs := p.client.Events(p.ctx, dockerTypes.EventsOptions{
Filters: filters.NewArgs(
filters.Arg("type", "container"),
filters.Arg("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", p.config.InstanceName)),
filters.Arg("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", p.instanceName)),
filters.Arg("label", "m1k1o.neko_rooms.proxy=true"),
filters.Arg("event", "create"),
filters.Arg("event", "start"),
//filters.Arg("event", "health_status"),
@@ -73,30 +63,32 @@ func (p *ProxyManagerCtx) Start() {
case err := <-errs:
p.logger.Info().Interface("err", err).Msg("eee")
case msg := <-msgs:
name, host, ok := p.parseLabels(msg.Actor.Attributes)
path, port, ok := p.parseLabels(msg.Actor.Attributes)
if !ok {
break
}
p.logger.Info().
Str("action", msg.Action).
Str("name", name).
Str("host", host).
Str("path", path).
Str("port", port).
Msg("new docker event")
p.mu.Lock()
switch msg.Action {
case "create":
p.handlers[name] = nil
p.handlers.Set(path, nil)
case "start":
p.handlers[name] = httputil.NewSingleHostReverseProxy(&url.URL{
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "http",
Host: host,
Host: msg.ID[:12] + ":" + port,
})
p.handlers.Set(path, proxy)
case "stop":
p.handlers[name] = nil
p.handlers.Set(path, nil)
case "destroy":
delete(p.handlers, name)
p.handlers.Remove(path)
}
p.mu.Unlock()
}
@@ -116,7 +108,7 @@ func (p *ProxyManagerCtx) Refresh() error {
containers, err := p.client.ContainerList(p.ctx, dockerTypes.ContainerListOptions{
All: true,
Filters: filters.NewArgs(
filters.Arg("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", p.config.InstanceName)),
filters.Arg("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", p.instanceName)),
),
})
@@ -124,63 +116,42 @@ func (p *ProxyManagerCtx) Refresh() error {
return err
}
p.handlers = map[string]*httputil.ReverseProxy{}
p.handlers = &prefixHandler{}
for _, cont := range containers {
name, host, ok := p.parseLabels(cont.Labels)
path, port, ok := p.parseLabels(cont.Labels)
if !ok {
continue
}
var proxy *httputil.ReverseProxy
if cont.State == "running" {
p.handlers[name] = httputil.NewSingleHostReverseProxy(&url.URL{
proxy = httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "http",
Host: host,
Host: cont.ID[:12] + ":" + port,
})
} else {
p.handlers[name] = nil
}
p.handlers.Set(path, proxy)
}
return nil
}
func (p *ProxyManagerCtx) parseLabels(labels map[string]string) (name string, host string, ok bool) {
name, ok = labels["m1k1o.neko_rooms.name"]
func (p *ProxyManagerCtx) parseLabels(labels map[string]string) (path string, port string, ok bool) {
path, ok = labels["m1k1o.neko_rooms.proxy.path"]
if !ok {
return
}
host, ok = labels["m1k1o.neko_rooms.host"]
port, ok = labels["m1k1o.neko_rooms.proxy.port"]
return
}
func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// only if has prefix
if !strings.HasPrefix(r.URL.Path, p.prefix) {
http.NotFound(w, r)
return
}
// remove prefix and leading /
roomPath := strings.TrimPrefix(r.URL.Path, p.prefix)
roomPath = strings.TrimLeft(roomPath, "/")
if roomPath == "" {
http.NotFound(w, r)
return
}
// get room name
roomName, doRedir := roomPath, false
if i := strings.Index(roomPath, "/"); i != -1 {
roomName = roomPath[:i]
} else {
doRedir = true
}
// get proxy by room name
p.mu.RLock()
proxy, ok := p.handlers[roomName]
proxy, prefix, ok := p.handlers.Match(r.URL.Path)
p.mu.RUnlock()
// if room not found
@@ -190,7 +161,7 @@ func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// redirect to room ending with /
if doRedir {
if r.URL.Path == prefix {
r.URL.Path += "/"
http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
return
@@ -208,12 +179,11 @@ func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) {
RoomNotReady(w, r)
}
p.logger.Err(err).Str("room", roomName).Msg("proxying error")
p.logger.Err(err).Str("prefix", prefix).Msg("proxying error")
}
// strip prefix from proxy
pathPrefix := path.Join(p.prefix, roomName)
handler := http.StripPrefix(pathPrefix, proxy)
handler := http.StripPrefix(prefix, proxy)
// handle by proxy
handler.ServeHTTP(w, r)
+94
View File
@@ -0,0 +1,94 @@
package proxy
import (
"net/http/httputil"
"strings"
)
type prefixHandler struct {
Value *httputil.ReverseProxy
Children map[string]*prefixHandler
}
func (p *prefixHandler) Set(prefix string, value *httputil.ReverseProxy) {
arr := strings.Split(prefix, "/")
len := len(arr)
for i, a := range arr {
if a == "" {
continue
}
if p.Children == nil {
p.Children = map[string]*prefixHandler{}
}
dat, ok := p.Children[a]
if !ok {
dat = &prefixHandler{}
}
if i == len-1 {
dat.Value = value
dat.Children = nil
}
p.Children[a] = dat
p = dat
}
}
func (p *prefixHandler) Remove(prefix string) {
arr := strings.Split(prefix, "/")
len := len(arr)
for i, a := range arr {
if a == "" {
continue
}
if i == len-1 {
delete(p.Children, a)
}
dat, ok := p.Children[a]
if !ok {
break
}
p = dat
}
// TODO: Remove all empty references.
}
func (p *prefixHandler) Match(path string) (*httputil.ReverseProxy, string, bool) {
arr := strings.Split(path, "/")
prefixArr := []string{}
for _, a := range arr {
if a == "" {
continue
}
pointer, ok := p.Children[a]
if !ok {
return nil, "", false
}
p = pointer
prefixArr = append(prefixArr, a)
// if leaf node
if p.Children == nil {
break
}
}
// if not leaf node
if p.Children != nil {
return nil, "", false
}
prefix := "/" + strings.Join(prefixArr, "/")
return p.Value, prefix, true
}
+3 -5
View File
@@ -30,7 +30,9 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab
url, ok := labels["m1k1o.neko_rooms.url"]
if !ok {
// It should be always available.
url = manager.config.GetRoomUrl(name)
//return nil, fmt.Errorf("damaged container labels: url not found")
}
nekoImage, ok := labels["m1k1o.neko_rooms.neko_image"]
@@ -113,15 +115,11 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab
func (manager *RoomManagerCtx) serializeLabels(labels RoomLabels) map[string]string {
labelsMap := map[string]string{
"m1k1o.neko_rooms.name": labels.Name,
"m1k1o.neko_rooms.url": manager.config.GetRoomUrl(labels.Name),
"m1k1o.neko_rooms.instance": manager.config.InstanceName,
"m1k1o.neko_rooms.neko_image": labels.NekoImage,
}
// Only when using traefik is the URL fixed with the room itself and not with neko-rooms.
if manager.config.Traefik.Enabled {
labelsMap["m1k1o.neko_rooms.url"] = manager.config.GetRoomUrl(labels.Name)
}
if labels.Mux && labels.Epr.Min == labels.Epr.Max {
labelsMap["m1k1o.neko_rooms.mux"] = fmt.Sprintf("%d", labels.Epr.Min)
} else {
+4 -2
View File
@@ -181,9 +181,9 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro
// Set traefik labels
//
if t := manager.config.Traefik; t.Enabled {
pathPrefix := path.Join("/", manager.config.PathPrefix, roomName)
if t := manager.config.Traefik; t.Enabled {
// create traefik rule
traefikRule := "PathPrefix(`" + pathPrefix + "`)"
if t.Domain != "" && t.Domain != "*" {
@@ -217,7 +217,9 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro
labels["traefik.http.routers."+containerName+".tls.certresolver"] = t.Certresolver
}
} else {
labels["m1k1o.neko_rooms.host"] = fmt.Sprintf("%s:%d", containerName, frontendPort)
labels["m1k1o.neko_rooms.proxy"] = "true"
labels["m1k1o.neko_rooms.proxy.path"] = pathPrefix
labels["m1k1o.neko_rooms.proxy.port"] = fmt.Sprintf("%d", frontendPort)
}
// add custom labels
+1 -1
View File
@@ -59,7 +59,7 @@ func New(ApiManager types.ApiManager, roomConfig *config.Room, config *config.Se
// rooms page
//
router.Route(roomConfig.PathPrefix, func(r chi.Router) {
router.Group(func(r chi.Router) {
if !roomConfig.Traefik.Enabled {
r.Handle("/*", proxyHandler)
} else {
+1 -1
View File
@@ -147,7 +147,7 @@ func (main *MainCtx) Start() {
main.proxyManager = proxy.New(
client,
main.Configs.Room,
main.Configs.Room.InstanceName,
)
main.proxyManager.Start()