Golang Http 庫指定 dns 伺服器進行解析

dmls 發表於 2021-04-30
Go

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 指向

更多原創文章乾貨分享,請關注公眾號
  • Golang Http 庫指定 dns 伺服器進行解析
  • 加微信實戰群請加微信(註明:實戰群):gocnio