原创分享 Golang Http 库指定 dns 服务器进行解析

dmls · 2021年04月30日 · 最后由 qinghe 回复于 2021年05月06日 · 413 次阅读
本帖已被设为精华帖!

GolangHttp 库指定 dns 服务器进行解析

前言

个人项目有个需求某个功能发送 http 请求的时候域名解析时走的是后台设置 dns 服务器。网上搜了下大多数都是使用第三方包并且没有融合到 http Client 中的例子.想了想 Go 底层肯定有实现了相关的功能的代码

开始行动

最开始的想法很简单使用第三方包定时解析域名放到 hosts 中,解决是解决了但是我这颗探究的心想让我深入探索下

通常的 http 请求最终执行的都是 http.Client.Do 这个方法。so 第一步就是翻阅这个方法的源码。

调用 send 来获取 resp。获取 resp 肯定是要发送请求的😁。

if resp, didTimeout, err = c.send(req, deadline); err != nil {
        // c.send() always closes req.Body
        reqBodyClosed = true
        if !deadline.IsZero() && didTimeout() {
            err = &httpError{
                // TODO: early in cycle: s/Client.Timeout exceeded/timeout or context cancellation/
                err:     err.Error() + " (Client.Timeout exceeded while awaiting headers)",
                timeout: true,
            }
        }
        return nil, uerr(err)
    }

进入方法内部发现他调用了一个同名的函数

resp, didTimeout, err = send(req, c.transport(), deadline)
if err != nil {
    return nil, didTimeout, err
}

追踪下去发现 resp 返回自参数中 rt 的 RoundTrip 方法

resp, err = rt.RoundTrip(req)
if err != nil {
    stopTimer()
    if resp != nil {
        log.Printf("RoundTripper returned a response & error; ignoring response")
    }

c.transport() 的代码如下

func (c *Client) transport() RoundTripper {
    if c.Transport != nil {
        return c.Transport
    }
    return DefaultTransport
}

根据上面的方法我们找到 DefaultTransport

var DefaultTransport RoundTripper = &Transport{
    Proxy: ProxyFromEnvironment,
    DialContext: (&net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
    }).DialContext,
    ForceAttemptHTTP2:     true,
    MaxIdleConns:          100,
    IdleConnTimeout:       90 * time.Second,
    TLSHandshakeTimeout:   10 * time.Second,
    ExpectContinueTimeout: 1 * time.Second,
}

很明显 DefaultTransport 是 Transport 的默认实现。进入Transport搜寻 RoundTrip方法。RoundTrip没找到但是找到了 roundTrip 并且这个函数备注上还写着 roundTrip implements a RoundTripper over HTTP. (wtf 小写的也算实现吗?)

进入roundTrip方法后由于调用层次太多我直接写出重要的 Transport中并没有解析 dns 的相关代码 而是调用Transport.DialContext建立 tcp 链接。

所以这里的重点要转入 net.Dialer.DialContext 方法,翻阅DialContext源码发现 dns 解析是在这里调用的

addrs, err := d.resolver().resolveAddrList(resolveCtx, "dial", network, address, d.LocalAddr)
if err != nil {
    return nil, &OpError{Op: "dial", Net: network, Source: nil, Addr: nil, Err: err}
}

点开 resolver 函数

func (d *Dialer) resolver() *Resolver {
    if d.Resolver != nil {
        return d.Resolver
    }
    return DefaultResolver
}

发现他跟 transport 方法没啥差别默认情况下返回 DefaultResolver,DefaultResolverResolver的默认实现。

由于 resolveAddrList 调用的层数太多了我就直说重点 Go 并没有提供指定 Dns 解析服务器的函数。但是我们可以通过配置Resolver的 Dial 函数来实现类似的功能

r.Dial = func(ctx context.Context, network, address string) (net.Conn, error) {
    d := net.Dialer{}
    address = "8.8.8.8:53"
    return d.DialContext(ctx, network, address)
}
addrs,err := r.LookupHost(context.Background(), "steamcommunity.com")
if err != nil{
    panic(err)
}
fmt.Println(addrs)

这相当于替换系统原来 dns 服务器地址换成我们的

结合到 http.Client

根据上面的那个内容照葫芦画瓢反向定义一波就行了 先定义 Transport

var SpecifiDnsServerTransport http.RoundTripper = &http.Transport{
    Proxy: http.ProxyFromEnvironment,
    DialContext: (&net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
        Resolver: &net.Resolver{PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
            d := net.Dialer{}
            address = "8.8.8.8:53"
            return d.DialContext(ctx, network, address)
        }},
    }).DialContext,
    ForceAttemptHTTP2:     true,
    MaxIdleConns:          100,
    IdleConnTimeout:       90 * time.Second,
    TLSHandshakeTimeout:   10 * time.Second,
    ExpectContinueTimeout: 1 * time.Second,
}

在定义 http.Client

var SpecifiDnsServerClient = http.Client{Transport: SpecifiDnsServerTransport}

然后就是你自己的业务代码了

最后

其实如果业务代码中需要单纯的获取域名解析 Resolver 下已经定义好了一堆方法 net也对Resolver进行函数封装了

addr, err := net.LookupHost("www.baidu.com")
if err != nil {
    panic(err)
}
fmt.Println(addr)

以上代码即可获取 www.baidu.com 的 A 跟 AAAA 指向

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
ningxiaofang1o GoCN 每日新闻 (2021-05-1) 中提及了此贴 05月01日 05:32
astaxie 将本帖设为了精华贴 05月05日 16:12

net/http/roundtrip.go里面,有这样的代码:

// RoundTrip implements the RoundTripper interface.
//
// For higher-level HTTP client support (such as handling of cookies
// and redirects), see Get, Post, and the Client type.
//
// Like the RoundTripper interface, the error types returned
// by RoundTrip are unspecified.
func (t *Transport) RoundTrip(req *Request) (*Response, error) {
    return t.roundTrip(req)
}

这里才是真正实现了接口的地方。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册