go-zgate/zgate.go
Zeev Diukman f7ca358b21 update
2025-03-07 07:31:22 +00:00

163 lines
4.8 KiB
Go

package zgate
import (
"context"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/zeevdiukman/go-reverseproxy"
"github.com/zeevdiukman/go-router"
"github.com/zeevdiukman/go-server"
"github.com/zeevdiukman/go-zgate/pkg/config"
)
const CERTS_PATH string = "./assets/certs/"
type Zgate struct {
Context context.Context
// Routers struct {
// Maps map[string]*router.HostRouter
// }
// map[string]*router.HostRouter
// Rules map[string]map[string]string
// Services map[string]string
Config *config.Config
DomainRouters DomainRouters
EntryPoints EntryPoints
ActiveEntryPoints ActiveEntryPoints
}
type ActiveEntryPoints map[string]bool
type DomainRouters map[string]*router.DomainRouter
type EntryPoints map[string]*EntryPoint
type EntryPoint struct {
*server.Server
*router.Router
HostRouters HostRouters
}
type HostRouters map[string]*router.DomainRouter
func New() *Zgate {
zGate := &Zgate{}
zGate.Context = context.Background()
zGate.Config = config.New()
zGate.initActiveEntryPoints()
zGate.DomainRouters = make(DomainRouters)
zGate.EntryPoints = make(EntryPoints)
return zGate
}
func (ep EntryPoints) ForEach(f func(string, *EntryPoint)) {
forEach(ep, f)
}
func forEach[K comparable, V any](mp map[K]V, f func(K, V)) {
for k, v := range mp {
f(k, v)
}
}
func (zGate *Zgate) initActiveEntryPoints() {
activeEntryPoints := make(ActiveEntryPoints)
zGate.Config.EntryPoints.ForEach(func(entryPointName string, entryPointConfig *config.EntryPoint) {
zGate.Config.HTTP.Routers.ForEach(func(rName string, rConfig *config.Router) {
if rConfig.EntryPoint == entryPointName {
activeEntryPoints[entryPointName] = true
}
})
})
zGate.ActiveEntryPoints = activeEntryPoints
}
func (zGate *Zgate) BuildActiveEntryPoints() {
zGate.Config.EntryPoints.ForEach(func(entryPointName string, entryPointConfig *config.EntryPoint) {
if isUsed, ok := zGate.ActiveEntryPoints[entryPointName]; ok && isUsed {
if !isOK(zGate.ActiveEntryPoints, entryPointName) {
zGate.EntryPoints = make(map[string]*EntryPoint)
}
r := router.NewRouter()
_, portStr, _ := strings.Cut(entryPointConfig.Address, ":")
port, _ := strconv.Atoi(portStr)
s := server.New().Name(entryPointName).Port(port)
s.Router(r)
zGate.EntryPoints[entryPointName] = &EntryPoint{
Server: s,
Router: r,
HostRouters: make(map[string]*router.DomainRouter),
}
}
})
}
func (zGate *Zgate) BuildRouters() {
zGate.Config.HTTP.Routers.ForEach(func(rName string, rConfig *config.Router) {
if isUsed, ok := zGate.ActiveEntryPoints[rConfig.EntryPoint]; ok && isUsed {
domain := rConfig.Rules.Map["Domain"].Value
serviceURL := zGate.Config.HTTP.Services[rConfig.Service].URL
if _, ok := zGate.EntryPoints[rConfig.EntryPoint]; ok {
entryPoint := zGate.EntryPoints[rConfig.EntryPoint]
if _, ok := entryPoint.HostRouters[domain]; !ok {
entryPoint.HostRouters = make(map[string]*router.DomainRouter)
}
rp := reverseproxy.New(zGate.Context, serviceURL)
rp.DirectorFunc(func(jup reverseproxy.JoinURLPathFunc) reverseproxy.DirectorFunc {
return defaultDirector(rp, jup)
})
r := zGate.EntryPoints[rConfig.EntryPoint].Router
hostRouter := r.NewDomainRouter(domain)
hostRouter.Handler(rp)
zGate.EntryPoints[rConfig.EntryPoint].HostRouters[domain] = hostRouter
}
}
})
}
func (zGate *Zgate) ListenAndServe() {
zGate.EntryPoints.ForEach(func(s string, zGateEntryPoint *EntryPoint) {
//TODO: TLS per domain
zGate.Config.HTTP.Routers.ForEach(func(rName string, rConfig *config.Router) {
if s == rConfig.EntryPoint {
}
})
zGateEntryPoint.CertKey(CERTS_PATH, "z.com.cert.pem", "z.com.key.pem")
go zGateEntryPoint.ListenAndServeTLS()
})
}
// func (activeEntryPoints ActiveEntryPoints) BuildActiveEntryPoint(entryPointName string, port int) {
// r := router.NewRouter()
// _, portStr, _ := strings.Cut(port, ":")
// port, _ := strconv.Atoi(portStr)
// s := server.New().Name(entryPointName).Port(port)
// s.Router(r)
// entryPoint = &EntryPoint{
// Server: s,
// Router: r,
// HostRouters: make(map[string]*router.DomainRouter),
// }
// }
func isOK[K comparable, V comparable](mp map[K]V, key K) bool {
if _, ok := mp[key]; !ok {
return false
}
return true
}
func defaultDirector(rpHandler *reverseproxy.ReverseProxy, jup reverseproxy.JoinURLPathFunc) func(*http.Request) {
return func(r *http.Request) {
r = r.WithContext(rpHandler.Context)
ctxKey := reverseproxy.CtxKey("host")
hostFromCtx := rpHandler.Context.Value(ctxKey).(string)
target, _ := url.Parse(hostFromCtx)
targetQuery := target.RawQuery
r.URL.Scheme = target.Scheme
r.URL.Host = target.Host
r.URL.Path, r.URL.RawPath = jup(target, r.URL)
if targetQuery == "" || r.URL.RawQuery == "" {
r.URL.RawQuery = targetQuery + r.URL.RawQuery
} else {
r.URL.RawQuery = targetQuery + "&" + r.URL.RawQuery
}
}
}