proxy.go

67 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
package router

import (
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	"sync"
)

var (
	proxiesMu sync.RWMutex
	proxies   = map[string]string{} // host → target (host:port)
)

// Proxy maps a domain to a backend target (e.g. "myapp:5000").
// Requests with this Host header are reverse-proxied to the target.
func Proxy(host, target string) {
	proxiesMu.Lock()
	proxies[strings.ToLower(host)] = target
	proxiesMu.Unlock()
}

// RemoveProxy removes a domain proxy.
func RemoveProxy(host string) {
	proxiesMu.Lock()
	delete(proxies, strings.ToLower(host))
	proxiesMu.Unlock()
}

// Proxies returns a copy of the current proxy table.
func Proxies() map[string]string {
	proxiesMu.RLock()
	defer proxiesMu.RUnlock()
	m := make(map[string]string, len(proxies))
	for k, v := range proxies {
		m[k] = v
	}
	return m
}

// Handler returns an http.Handler that does domain-based proxy routing.
// Requests matching a Proxy() entry are reverse-proxied to the target.
// All other requests fall through to the provided handler.
func Handler(fallback http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		host := stripPort(r.Host)

		proxiesMu.RLock()
		target, ok := proxies[host]
		proxiesMu.RUnlock()

		if !ok {
			fallback.ServeHTTP(w, r)
			return
		}

		u := &url.URL{Scheme: "http", Host: target}
		proxy := httputil.NewSingleHostReverseProxy(u)
		proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
			log.Printf("router: proxy %s → %s: %v", host, target, err)
			http.Error(w, "Service unavailable", http.StatusBadGateway)
		}
		proxy.ServeHTTP(w, r)
	})
}