This commit is contained in:
Zeev Diukman 2025-03-07 07:31:22 +00:00
parent 7d9181c26a
commit f7ca358b21
2 changed files with 381 additions and 33 deletions

241
pkg/config/config.go Normal file
View file

@ -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
}

175
zgate.go
View file

@ -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"
// }
// e._IP = net.ParseIP(ipAddress)
// return e
// }
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 {
func newEntryPoint(z *Zgate, entryPointName string) *EntryPoint {
e := &EntryPoint{}
z.EntryPoints[entryPointName] = e
return e
}
})
zGateEntryPoint.CertKey(CERTS_PATH, "z.com.cert.pem", "z.com.key.pem")
go zGateEntryPoint.ListenAndServeTLS()
})
}
// func (z *EntryPoint) initServer() *EntryPoint{
// server.New()
// return z.isTLS
// 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
// }
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
}
}
}