2026-04-13 20:28:10 -07:00

115 lines
2.6 KiB
Go

package main
// This is a runable example which will spawn two servers, one we can access
// which hits the other and response with the data provided.
import (
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"sync"
)
func main() {
upstreamHost, err := findNonLoopbackIPv4()
if err != nil {
log.Fatal(err)
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
if err := startUpstream(upstreamHost); err != nil {
log.Fatal(err)
}
}()
go func() {
defer wg.Done()
if err := startFrontend(upstreamHost); err != nil {
log.Fatal(err)
}
}()
wg.Wait()
}
func startFrontend(upstreamHost string) error {
mux := http.NewServeMux()
mux.HandleFunc("/echo", func(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
message := req.URL.Query().Get("message")
upstreamURL := fmt.Sprintf("http://%s:3001/echo?message=%s", upstreamHost, url.QueryEscape(message))
resp, err := http.Get(upstreamURL)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(resp.StatusCode)
_, _ = w.Write(body)
})
log.Printf("frontend listening on http://127.0.0.1:3000/echo?message=hello")
log.Printf("frontend calls upstream at http://%s:3001/echo", upstreamHost)
return http.ListenAndServe("127.0.0.1:3000", mux)
}
func startUpstream(upstreamHost string) error {
mux := http.NewServeMux()
mux.HandleFunc("/echo", func(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
message := req.URL.Query().Get("message")
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = w.Write([]byte(message))
})
log.Printf("upstream listening on http://%s:3001/echo?message=hello", upstreamHost)
return http.ListenAndServe(":3001", mux)
}
func findNonLoopbackIPv4() (string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok || ipNet.IP == nil {
continue
}
ip := ipNet.IP.To4()
if ip == nil || ip.IsLoopback() {
continue
}
return ip.String(), nil
}
return "", fmt.Errorf("no non-loopback IPv4 address found; this demo needs one so outbound traffic does not bypass the proxy")
}