// Override http.Client host to ip address or separate host // Basically a programic equivalent of `/etc/hosts` in go for http clients package main import ( "context" "fmt" "io/ioutil" "net" "net/http" "time" ) // hostOverrides is a map of hosts or ips to replace override with the value given. // The value of each key can be an ip address or separate hostname to lookup. var hostOverrides = map[string]string{ "google.com": "192.168.0.1", } func main() { myClient := &http.Client{ Transport: NewOverrideHostTransport(hostOverrides), } resp, err := myClient.Get("https://google.com") if err != nil { panic("error making req: " + err.Error()) } defer resp.Body.Close() data, err := ioutil.ReadAll(resp.Body) if err != nil { panic("error reading resp: " + err.Error()) } fmt.Println("Response") fmt.Println(string(data)) } // NewOverrideHostTransport creates a new transport which overrides hostnames. func NewOverrideHostTransport(overrides map[string]string) http.RoundTripper { // Start with default transport since it has many default proxy and timeout options you want. myTransport := http.DefaultTransport.(*http.Transport).Clone() myDialer := &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, DualStack: true, } myTransport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { fmt.Println("Resolving address:", network, address) // address contains `host:port` ie `google.com:443` host, port, err := net.SplitHostPort(address) if err != nil { return nil, err } // Lookup to see if we have a host override newHost, ok := overrides[host] if !ok { fmt.Println("No override found for host, using original", host) return myDialer.DialContext(ctx, network, address) } overrideAddress := net.JoinHostPort(newHost, port) fmt.Println("Overriding address:", address, overrideAddress) return myDialer.DialContext(ctx, network, overrideAddress) } return myTransport }