From f7ca358b210761b093e864c7f09a78a5f60c403a Mon Sep 17 00:00:00 2001 From: Zeev Diukman Date: Fri, 7 Mar 2025 07:31:22 +0000 Subject: [PATCH] update --- pkg/config/config.go | 241 +++++++++++++++++++++++++++++++++++++++++++ zgate.go | 173 +++++++++++++++++++++++++------ 2 files changed, 381 insertions(+), 33 deletions(-) create mode 100644 pkg/config/config.go diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..d1ecee5 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,241 @@ +package config + +import ( + "regexp" + "strings" + + "github.com/zeevdiukman/go-config" +) + +const VIPER_NAME string = "config" +const VIPER_TYPE string = "yaml" +const VIPER_PATH string = "./assets/config/." +const SERVICES_KEY string = "http.services" +const ROUTERS_KEY string = "http.routers" +const ENTRYPOINTS_KEY string = "entrypoints" +const TLS_KEY string = "tls.certproviders" + +type Config struct { + HTTP *HTTP + TLS *TLS + EntryPoints EntryPoints +} +type HTTP struct { + Routers Routers + Services Services +} +type TLS struct { + CertProviders CertProviders +} +type CertProviders map[string]*CertProvider +type CertProvider struct { + Key string + Cert string +} + +type Services map[string]*Service +type Service struct { + URL string +} +type EntryPoints map[string]*EntryPoint +type EntryPoint struct { + Address string + TLS *EntryPointTLS +} +type EntryPointTLS struct { + Enabled bool +} +type Routers map[string]*Router +type Router struct { + EntryPoint string + Service string + Rules *Rules + TLS *RouterTLS +} +type RouterTLS struct { + CertProvider string +} +type Rules struct { + Raw string + Map map[string]*Rule +} + +type Rule struct { + Name string + Value string +} + +func New() *Config { + + c := config.NewConfig(VIPER_NAME, VIPER_TYPE, VIPER_PATH) + c.Load() + + appConfig := &Config{ + HTTP: &HTTP{ + Routers: Routers{}, + Services: Services{}, + }, + TLS: &TLS{ + CertProviders: CertProviders{}, + }, + EntryPoints: EntryPoints{}, + } + + appConfig.InitRouters(c) + appConfig.InitServices(c) + appConfig.InitEntryPoints(c) + appConfig.InitTLS(c) + + return appConfig +} +func (r Routers) ForEach(fn func(routerName string, routerConfig *Router)) { + for k, v := range r { + fn(k, v) + } +} +func (ep EntryPoints) ForEach(fn func(entryPointName string, entryPointConfig *EntryPoint)) { + forEach(ep, fn) +} +func (s Services) ForEach(fn func(serviceName string, serviceConfig *Service)) { + forEach(s, fn) +} +func (c *Config) InitRouters(vConfig *config.Config) { + c.HTTP.Routers = make(Routers) + for k, v := range vConfig.Sub(ROUTERS_KEY).AllSettings() { + val := v.(map[string]any) + + var tls string = "" + var services string = "" + var rawRules string = "" + var entryPoint string = "" + + if value, ok := val["service"]; ok { + services = value.(string) + } + if value, ok := val["rules"]; ok { + rawRules = value.(string) + } + if value, ok := val["entrypoint"]; ok { + entryPoint = value.(string) + } + if value, ok := val["tls"]; ok { + tls = value.(map[string]any)["certprovider"].(string) + } + rls := &Rules{ + Raw: rawRules, + Map: make(map[string]*Rule), + } + + r := &Router{ + Service: services, + Rules: rls, + EntryPoint: entryPoint, + TLS: &RouterTLS{CertProvider: tls}, + } + r.ForRuleFunc("Domain", func(domain string) { + r.Rules.Map["Domain"] = &Rule{ + Name: "Domain", + Value: domain, + } + }) + r.ForRuleFunc("Path", func(path string) { + r.Rules.Map["Path"] = &Rule{ + Name: "Path", + Value: path, + } + }) + + c.HTTP.Routers[k] = r + } +} +func (c *Config) InitServices(vConfig *config.Config) { + c.HTTP.Services = make(map[string]*Service) + for k, v := range vConfig.Sub(SERVICES_KEY).AllSettings() { + c.HTTP.Services[k] = &Service{ + URL: v.(string), + } + } +} +func (c *Config) InitEntryPoints(vConfig *config.Config) { + c.EntryPoints = make(EntryPoints) + for k, v := range vConfig.Sub(ENTRYPOINTS_KEY).AllSettings() { + v := v.(map[string]any) + address := "" + enabled := false + if v, ok := v["tls"]; ok { + if v, ok := v.(map[string]any)["enabled"]; ok { + enabled = v.(bool) + } + } + + if value, ok := v["address"]; ok { + address = value.(string) + } + c.EntryPoints[k] = &EntryPoint{ + Address: address, + TLS: &EntryPointTLS{ + Enabled: enabled, + }, + } + } +} + +func (c *Config) InitTLS(vConfig *config.Config) { + c.TLS.CertProviders = make(map[string]*CertProvider) + + for k, v := range vConfig.Sub(TLS_KEY).AllSettings() { + cert := v.(map[string]any)["cert"].(string) + key := v.(map[string]any)["key"].(string) + c.TLS.CertProviders[k] = &CertProvider{ + Cert: cert, + Key: key, + } + } +} +func forEach[K comparable, V comparable](mp map[K]V, fn func(key K, value V)) { + for k, v := range mp { + fn(k, v) + } +} + +func (r *Router) ForRuleFunc(ruleName string, fn func(string)) { + rules := r.Rules.Raw + rulesSlice := splitStatementByRegex(rules, "\\(`(.*?)`\\)") + for idx, rule := range rulesSlice { + rule, _ = strings.CutPrefix(rule, ".") + if rule == ruleName { + fn(rulesSlice[idx+1]) + } + } +} + +func splitStatementByRegex(str string, regx string) []string { + + input := str + re := regexp.MustCompile(regx) + matches := re.FindAllStringSubmatch(input, -1) + if len(matches) == 0 { + return []string{str} // Return original if no backticks found + } + + result := []string{} + lastIndex := 0 + + for _, match := range matches { + + startIndex := strings.Index(input, match[0]) + + if startIndex > lastIndex { + result = append(result, input[lastIndex:startIndex]) + } + + result = append(result, match[1]) // Append the content within backticks + lastIndex = startIndex + len(match[0]) + } + + if lastIndex < len(input) { + result = append(result, input[lastIndex:]) + } + + return result +} diff --git a/zgate.go b/zgate.go index 1ce1aea..49819f0 100644 --- a/zgate.go +++ b/zgate.go @@ -1,56 +1,163 @@ 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 { - EntryPoints map[string]*EntryPoint - // TLS *TLS + 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 { - Name string - // Port int - // _IP net.IP *server.Server - isTLS bool + *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 } -// type TLS struct { -// CertPath string -// CertFile string -// KeyFile string -// } +func (ep EntryPoints) ForEach(f func(string, *EntryPoint)) { + forEach(ep, f) +} -func NewGate() *Zgate { - return &Zgate{ - EntryPoints: make(map[string]*EntryPoint), +func forEach[K comparable, V any](mp map[K]V, f func(K, V)) { + for k, v := range mp { + f(k, v) } } -func (z *Zgate) NewEntryPoint(entryPointName string) *EntryPoint { - e := newEntryPoint(z, entryPointName) - e.Server = server.New() - return e +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 (e *EntryPoint) IP(ipAddress string) *EntryPoint { -// if ipAddress == "" { -// ipAddress = "127.0.0.1" +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), // } -// e._IP = net.ParseIP(ipAddress) -// return e // } -func newEntryPoint(z *Zgate, entryPointName string) *EntryPoint { - e := &EntryPoint{} - z.EntryPoints[entryPointName] = e - return e +func isOK[K comparable, V comparable](mp map[K]V, key K) bool { + if _, ok := mp[key]; !ok { + return false + } + return true } -// func (z *EntryPoint) initServer() *EntryPoint{ -// server.New() -// return z.isTLS -// } -// func +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 + } + } +}