package router import ( "log" "net/http" "net/http/httputil" "strings" "sync" "github.com/user/go-proxy/internal/config" "github.com/user/go-proxy/internal/proxy" ) // routeEntry stores the configuration and its pre-compiled ReverseProxy type routeEntry struct { config config.RouterRoute proxy *httputil.ReverseProxy } type Router struct { mu sync.RWMutex routes []routeEntry } func NewRouter(cfg config.HTTPConfig) *Router { r := &Router{} r.UpdateConfig(cfg) return r } func (r *Router) UpdateConfig(cfg config.HTTPConfig) { var newRoutes []routeEntry for _, routeCfg := range cfg.Routers { // Collect backends (support both Backends array and single BackendURL) backends := routeCfg.Backends if len(backends) == 0 && routeCfg.BackendURL != "" { backends = []string{routeCfg.BackendURL} } if len(backends) == 0 { log.Printf("Warning: Route %s has no backends configured, skipping", routeCfg.Path) continue } p, err := proxy.NewLoadBalancedHTTPProxy(backends) if err != nil { log.Printf("Error creating proxy for route %s: %v", routeCfg.Path, err) continue } newRoutes = append(newRoutes, routeEntry{ config: routeCfg, proxy: p, }) } r.mu.Lock() defer r.mu.Unlock() r.routes = newRoutes } func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { r.mu.RLock() routes := r.routes r.mu.RUnlock() for _, route := range routes { cfg := route.config if cfg.Host != "" && req.Host != cfg.Host { continue } if strings.HasPrefix(req.URL.Path, cfg.Path) { if cfg.StripPrefix { req.URL.Path = strings.TrimPrefix(req.URL.Path, cfg.Path) if req.URL.Path == "" || req.URL.Path[0] != '/' { req.URL.Path = "/" + req.URL.Path } } // Serve using the pre-compiled proxy route.proxy.ServeHTTP(w, req) return } } http.Error(w, "Not Found", http.StatusNotFound) }