Custom DNS
By default Req uses the system resolver to translate hostnames into IP
addresses. For use cases such as crawling a known list of hosts, system DNS
lookups on dead hosts can cause long timeouts. This article shows how to
customize DNS resolution with a specific DNS server or a static hostname→IP
mapping.
The following APIs only apply to HTTP/1 and HTTP/2 (the same scope as
Client.SetDial). HTTP/3 is unchanged.
Custom DNS Server
Use Client.SetResolver to route DNS queries to a specific server:
import "net"
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "udp", "1.1.1.1:53")
},
}
client := req.C().SetResolver(resolver)
SetResolver is implemented via SetDial with a net.Dialer that carries the
resolver. If r is nil, the default resolver is used.
Static Hosts Mapping
Use Client.SetHosts to provide a hosts-file-style hostname→IP mapping. This is
fail-closed: hostnames not present in the map fail immediately with a
no such host error and never hit the system resolver, which avoids long DNS
timeouts when crawling with a known host list.
client := req.C().SetHosts(map[string]string{
"api.internal": "10.0.0.5",
"db.internal": "10.0.0.6",
"v6.internal": "::1", // brackets like "[::1]" are also accepted
})
Behavior notes:
- Keys are hostnames only (no port). Matching is case-insensitive and IDNA-normalized to match the dial address form used by the transport.
- Values must be literal IP addresses (IPv4 or IPv6). Non-IP values never fall through to system DNS; dialing that host returns a clear error instead.
- IP-literal request URLs (e.g.
https://1.2.3.4/) skip the map and dial directly. - An empty or nil map makes every non-literal hostname fail closed.
- Proxy routing is rejected while
SetHostsis active, because a proxy can resolve the destination remotely and bypass the static mapping. - The map is copied; later changes to the caller’s map are ignored.
Replacing Each Other
SetDial, SetResolver, SetHosts, and SetUnixSocket all configure the
underlying dialer and replace each other (the last call wins). For example,
if you call SetHosts and then SetDial, the custom dial function from
SetDial is used.
// SetDial replaces the hosts dialer installed by SetHosts
client := req.C().SetHosts(map[string]string{"api.internal": "10.0.0.5"})
client.SetDial(func(ctx context.Context, network, addr string) (net.Conn, error) {
// ...
})