mirror of
https://github.com/m1k1o/neko-rooms.git
synced 2026-11-02 22:51:34 -06:00
proxy store context in labels.
This commit is contained in:
+41
-71
@@ -6,8 +6,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
dockerTypes "github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
@@ -15,35 +13,26 @@ 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/internal/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyManagerCtx struct {
|
type ProxyManagerCtx struct {
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel func()
|
cancel func()
|
||||||
prefix string
|
|
||||||
client *dockerClient.Client
|
client *dockerClient.Client
|
||||||
config *config.Room
|
instanceName string
|
||||||
handlers map[string]*httputil.ReverseProxy
|
handlers *prefixHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(client *dockerClient.Client, config *config.Room) *ProxyManagerCtx {
|
func New(client *dockerClient.Client, instanceName string) *ProxyManagerCtx {
|
||||||
logger := log.With().Str("module", "proxy").Logger()
|
|
||||||
|
|
||||||
prefix := config.PathPrefix
|
|
||||||
if !strings.HasSuffix(prefix, "/") {
|
|
||||||
prefix += "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ProxyManagerCtx{
|
return &ProxyManagerCtx{
|
||||||
logger: logger,
|
logger: log.With().Str("module", "proxy").Logger(),
|
||||||
prefix: prefix,
|
|
||||||
client: client,
|
client: client,
|
||||||
config: config,
|
instanceName: instanceName,
|
||||||
handlers: map[string]*httputil.ReverseProxy{},
|
handlers: &prefixHandler{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +48,8 @@ func (p *ProxyManagerCtx) Start() {
|
|||||||
msgs, errs := p.client.Events(p.ctx, dockerTypes.EventsOptions{
|
msgs, errs := p.client.Events(p.ctx, dockerTypes.EventsOptions{
|
||||||
Filters: filters.NewArgs(
|
Filters: filters.NewArgs(
|
||||||
filters.Arg("type", "container"),
|
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", "create"),
|
||||||
filters.Arg("event", "start"),
|
filters.Arg("event", "start"),
|
||||||
//filters.Arg("event", "health_status"),
|
//filters.Arg("event", "health_status"),
|
||||||
@@ -73,30 +63,32 @@ func (p *ProxyManagerCtx) Start() {
|
|||||||
case err := <-errs:
|
case err := <-errs:
|
||||||
p.logger.Info().Interface("err", err).Msg("eee")
|
p.logger.Info().Interface("err", err).Msg("eee")
|
||||||
case msg := <-msgs:
|
case msg := <-msgs:
|
||||||
name, host, ok := p.parseLabels(msg.Actor.Attributes)
|
path, port, ok := p.parseLabels(msg.Actor.Attributes)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
p.logger.Info().
|
p.logger.Info().
|
||||||
Str("action", msg.Action).
|
Str("action", msg.Action).
|
||||||
Str("name", name).
|
Str("path", path).
|
||||||
Str("host", host).
|
Str("port", port).
|
||||||
Msg("new docker event")
|
Msg("new docker event")
|
||||||
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
switch msg.Action {
|
switch msg.Action {
|
||||||
case "create":
|
case "create":
|
||||||
p.handlers[name] = nil
|
p.handlers.Set(path, nil)
|
||||||
case "start":
|
case "start":
|
||||||
p.handlers[name] = httputil.NewSingleHostReverseProxy(&url.URL{
|
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
|
||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
Host: host,
|
Host: msg.ID[:12] + ":" + port,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
p.handlers.Set(path, proxy)
|
||||||
case "stop":
|
case "stop":
|
||||||
p.handlers[name] = nil
|
p.handlers.Set(path, nil)
|
||||||
case "destroy":
|
case "destroy":
|
||||||
delete(p.handlers, name)
|
p.handlers.Remove(path)
|
||||||
}
|
}
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
}
|
}
|
||||||
@@ -116,7 +108,7 @@ func (p *ProxyManagerCtx) Refresh() error {
|
|||||||
containers, err := p.client.ContainerList(p.ctx, dockerTypes.ContainerListOptions{
|
containers, err := p.client.ContainerList(p.ctx, dockerTypes.ContainerListOptions{
|
||||||
All: true,
|
All: true,
|
||||||
Filters: filters.NewArgs(
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.handlers = map[string]*httputil.ReverseProxy{}
|
p.handlers = &prefixHandler{}
|
||||||
|
|
||||||
for _, cont := range containers {
|
for _, cont := range containers {
|
||||||
name, host, ok := p.parseLabels(cont.Labels)
|
path, port, ok := p.parseLabels(cont.Labels)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var proxy *httputil.ReverseProxy
|
||||||
if cont.State == "running" {
|
if cont.State == "running" {
|
||||||
p.handlers[name] = httputil.NewSingleHostReverseProxy(&url.URL{
|
proxy = httputil.NewSingleHostReverseProxy(&url.URL{
|
||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
Host: host,
|
Host: cont.ID[:12] + ":" + port,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
p.handlers[name] = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.handlers.Set(path, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProxyManagerCtx) parseLabels(labels map[string]string) (name string, host string, ok bool) {
|
func (p *ProxyManagerCtx) parseLabels(labels map[string]string) (path string, port string, ok bool) {
|
||||||
name, ok = labels["m1k1o.neko_rooms.name"]
|
path, ok = labels["m1k1o.neko_rooms.proxy.path"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
host, ok = labels["m1k1o.neko_rooms.host"]
|
port, ok = labels["m1k1o.neko_rooms.proxy.port"]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
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
|
// get proxy by room name
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
proxy, ok := p.handlers[roomName]
|
proxy, prefix, ok := p.handlers.Match(r.URL.Path)
|
||||||
p.mu.RUnlock()
|
p.mu.RUnlock()
|
||||||
|
|
||||||
// if room not found
|
// if room not found
|
||||||
@@ -190,7 +161,7 @@ func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// redirect to room ending with /
|
// redirect to room ending with /
|
||||||
if doRedir {
|
if r.URL.Path == prefix {
|
||||||
r.URL.Path += "/"
|
r.URL.Path += "/"
|
||||||
http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
|
http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
@@ -208,12 +179,11 @@ func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
RoomNotReady(w, r)
|
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
|
// strip prefix from proxy
|
||||||
pathPrefix := path.Join(p.prefix, roomName)
|
handler := http.StripPrefix(prefix, proxy)
|
||||||
handler := http.StripPrefix(pathPrefix, proxy)
|
|
||||||
|
|
||||||
// handle by proxy
|
// handle by proxy
|
||||||
handler.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -30,7 +30,9 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab
|
|||||||
|
|
||||||
url, ok := labels["m1k1o.neko_rooms.url"]
|
url, ok := labels["m1k1o.neko_rooms.url"]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
// It should be always available.
|
||||||
url = manager.config.GetRoomUrl(name)
|
url = manager.config.GetRoomUrl(name)
|
||||||
|
//return nil, fmt.Errorf("damaged container labels: url not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
nekoImage, ok := labels["m1k1o.neko_rooms.neko_image"]
|
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 {
|
func (manager *RoomManagerCtx) serializeLabels(labels RoomLabels) map[string]string {
|
||||||
labelsMap := map[string]string{
|
labelsMap := map[string]string{
|
||||||
"m1k1o.neko_rooms.name": labels.Name,
|
"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.instance": manager.config.InstanceName,
|
||||||
"m1k1o.neko_rooms.neko_image": labels.NekoImage,
|
"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 {
|
if labels.Mux && labels.Epr.Min == labels.Epr.Max {
|
||||||
labelsMap["m1k1o.neko_rooms.mux"] = fmt.Sprintf("%d", labels.Epr.Min)
|
labelsMap["m1k1o.neko_rooms.mux"] = fmt.Sprintf("%d", labels.Epr.Min)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -181,9 +181,9 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro
|
|||||||
// Set traefik labels
|
// Set traefik labels
|
||||||
//
|
//
|
||||||
|
|
||||||
if t := manager.config.Traefik; t.Enabled {
|
pathPrefix := path.Join("/", manager.config.PathPrefix, roomName)
|
||||||
pathPrefix := path.Join("/", manager.config.PathPrefix, roomName)
|
|
||||||
|
|
||||||
|
if t := manager.config.Traefik; t.Enabled {
|
||||||
// create traefik rule
|
// create traefik rule
|
||||||
traefikRule := "PathPrefix(`" + pathPrefix + "`)"
|
traefikRule := "PathPrefix(`" + pathPrefix + "`)"
|
||||||
if t.Domain != "" && t.Domain != "*" {
|
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
|
labels["traefik.http.routers."+containerName+".tls.certresolver"] = t.Certresolver
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// add custom labels
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func New(ApiManager types.ApiManager, roomConfig *config.Room, config *config.Se
|
|||||||
// rooms page
|
// rooms page
|
||||||
//
|
//
|
||||||
|
|
||||||
router.Route(roomConfig.PathPrefix, func(r chi.Router) {
|
router.Group(func(r chi.Router) {
|
||||||
if !roomConfig.Traefik.Enabled {
|
if !roomConfig.Traefik.Enabled {
|
||||||
r.Handle("/*", proxyHandler)
|
r.Handle("/*", proxyHandler)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user