自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go 1.7 相比 Go 1.6 有哪些值得注意的改動?

開發(fā) 前端
運行這段代碼,你將看到控制臺輸出 DNS 查詢、TCP 連接、TLS 握手等階段的開始和結(jié)束信息,以及它們的耗時。這對于定位 HTTP 請求中的性能瓶頸非常有價值。

https://go.dev/doc/go1.7

Go 1.7 值得關(guān)注的改動:

  1. 語言規(guī)范微調(diào): 明確了語句列表的終止語句是以“最后一個非空語句”為準,這與編譯器 gc 和 gccgo 的現(xiàn)有行為一致,對現(xiàn)有代碼沒有影響。之前的定義僅指“最終語句”,導(dǎo)致尾隨空語句的效果不明確。
  2. 平臺支持: 新增了對 macOS 10.12 Sierra 的支持(注意:低于 Go 1.7 構(gòu)建的二進制文件在 Sierra 上可能無法正常工作)。增加了對 Linux on z Systems (linux/s390x) 的實驗性移植。同時更新了對 MIPS64、PowerPC 和 OpenBSD 的支持。
  3. Cgo 改進: 使用 Cgo 的包現(xiàn)在可以包含 Fortran 源文件。新增了 C.CBytes 輔助函數(shù)用于 []byte 到 C 的 void* 轉(zhuǎn)換。同時,在配合較新版本的 GCC 或 Clang 時,Cgo 構(gòu)建的確定性得到了提升。
  4. Context 包: 將 golang.org/x/net/context 包引入標準庫,成為 context 包,用于在 API 邊界之間傳遞請求范圍的值、取消信號和超時。此變更使得包括 net, net/http, 和 os/exec 在內(nèi)的標準庫包也能利用 context。
  5. HTTP 追蹤: 新增 net/http/httptrace 包,提供了在 HTTP 請求內(nèi)部追蹤事件的機制,方便開發(fā)者診斷和分析 HTTP 請求的生命周期細節(jié)。

下面是一些值得展開的討論:

Cgo 改進:支持 Fortran、新增 C.CBytes 及構(gòu)建確定性

Go 1.7 對 Cgo (Cgo) 進行了幾項改進:

  1. Fortran 支持:現(xiàn)在,使用 Cgo 的 Go 包可以直接包含 Fortran 語言編寫的源文件(.f, .F, .f90, .F90, .f95, .F95)。不過,Go 代碼與 Fortran 代碼交互時,仍然需要通過 C 語言的 API 作為橋梁。
  2. 新增 C.CBytes 輔助函數(shù):
  • 之前,如果想把 Go 的 string 傳遞給 C 函數(shù)(通常是 char* 類型),可以使用 C.CString。這個函數(shù)會在 C 的內(nèi)存堆上分配空間,并將 Go 字符串的內(nèi)容(包括結(jié)尾的 \0)復(fù)制過去,返回一個 *C.char。開發(fā)者需要記得在使用完畢后調(diào)用 C.free 來釋放這塊內(nèi)存。
  • Go 1.7 新增了 C.CBytes 函數(shù)。它接受一個 Go 的 []byte 切片,返回一個 unsafe.Pointer(對應(yīng) C 的 void*)。與 C.CString 不同,C.CBytes 不會 復(fù)制數(shù)據(jù),而是直接返回指向 Go 切片底層數(shù)組的指針。關(guān)鍵在于:這個指針指向的是 Go 的內(nèi)存,其生命周期由 Go 的垃圾回收器管理。這意味著這個 unsafe.Pointer 通常只在 C 函數(shù)調(diào)用的短暫期間內(nèi)有效。C 代碼不應(yīng)該持有這個指針長期使用,因為它指向的內(nèi)存可能隨時被 Go GC 回收或移動。C.CBytes 的主要優(yōu)勢在于避免了內(nèi)存分配和數(shù)據(jù)復(fù)制,提高了性能,特別適用于 C 函數(shù)只需要臨時讀取 Go 字節(jié)數(shù)據(jù)的場景。

下面是一個使用 C.CBytes 的例子:

假設(shè)我們有一個 C 函數(shù),它接收一個字節(jié)緩沖區(qū)和長度:

// #include <stdio.h>
// #include <string.h>
//
// void process_data(void* data, size_t len) {
//     char buf[100];
//     // 注意:這里只是讀取數(shù)據(jù),并且假設(shè) len 不會超長
//     memcpy(buf, data, len < 99 ? len : 99);
//     buf[len < 99 ? len : 99] = '\0';
//     printf("C received: %s (length: %zu)\n", buf, len);
// }
import "C"
import (
    "fmt"
    "unsafe"
)

func main() {
    goBytes := []byte("Hello from Go Slice!")

    // 將 Go []byte 傳遞給 C 函數(shù)
    // C.CBytes 返回 unsafe.Pointer,對應(yīng) C 的 void*
    // C 函數(shù)接收數(shù)據(jù)指針和長度
    C.process_data(C.CBytes(goBytes), C.size_t(len(goBytes)))

    fmt.Println("Go function finished.")

    // 注意:goBytes 的內(nèi)存在 Go 中管理,不需要手動 free
    // C.CBytes 返回的指針僅在 C.process_data 調(diào)用期間保證有效
}

運行上述 Go 程序(需要 C 編譯器環(huán)境),C 函數(shù) process_data 將能正確接收并打印 Go 傳遞過來的字節(jié)數(shù)據(jù)。

  1. 構(gòu)建確定性提升:
  • 在 Go 1.7 之前,使用 Cgo 構(gòu)建包或二進制文件時,每次構(gòu)建的結(jié)果(二進制內(nèi)容)可能都不同。這主要是因為構(gòu)建過程中會涉及到一些臨時目錄,而這些臨時目錄的路徑會被嵌入到最終的調(diào)試信息中。
  • Go 1.7 利用了較新版本 C 編譯器(如 GCC 或 Clang)提供的一個特性:-fdebug-prefix-map 選項。這個選項允許將源碼或構(gòu)建時的路徑映射到一個固定的、與環(huán)境無關(guān)的前綴。當 Go 的構(gòu)建工具鏈檢測到可用的 C 編譯器支持此選項時,就會使用它來處理 Cgo 生成的 C 代碼編譯過程中的路徑信息。
  • 其結(jié)果是,只要輸入的 Go 源碼、依賴庫和構(gòu)建工具鏈版本相同,并且使用了支持該選項的 C 編譯器,那么重復(fù)構(gòu)建產(chǎn)生的二進制文件內(nèi)容將是完全一致的。這種 確定性構(gòu)建 (deterministic builds) 對于依賴二進制文件哈希進行驗證、緩存或分發(fā)的場景非常重要。

Context 包:標準化請求范圍管理與取消機制

Go 1.7 最重要的變化之一是將原先位于擴展庫 golang.org/x/net/context 的 context 包正式引入標準庫。這標志著 Go 語言在處理并發(fā)、超時和請求數(shù)據(jù)傳遞方面有了統(tǒng)一的、官方推薦的模式。

為什么需要 context?

在典型的 Go 服務(wù)器應(yīng)用中,每個請求通常在一個單獨的 協(xié)程 (goroutine) 中處理。處理請求的過程中,可能需要啟動更多的 goroutine 來訪問數(shù)據(jù)庫、調(diào)用其他 RPC 服務(wù)等。這些為同一個請求工作的 goroutine 集合通常需要共享一些信息,例如:

  • 用戶的身份標識或授權(quán)令牌。
  • 請求的截止時間 (deadline)。
  • 一個取消信號,當原始請求被取消(如用戶關(guān)閉連接)或超時時,所有相關(guān)的 goroutine 都應(yīng)該盡快停止工作,釋放資源。

context 包就是為了解決這些問題而設(shè)計的。它提供了一種在 API 調(diào)用鏈中傳遞 請求范圍的值 (request-scoped values) 、 取消信號 (cancellation signals) 和 截止時間 (deadlines) 的標準方法。

核心接口 context.Context

package context

import "time"

type Context interface {
    // Deadline 返回此 Context 被取消的時間,如果沒有設(shè)置 Deadline,ok 返回 false。
    Deadline() (deadline time.Time, ok bool)

    // Done 返回一個 channel,當 Context 被取消或超時時,該 channel 會被關(guān)閉。
    // 多次調(diào)用 Done 會返回同一個 channel。
    // 如果 Context 永不取消,Done 可能返回 nil。
    Done() <-chan struct{}

    // Err 在 Done channel 關(guān)閉后,返回 Context 被取消的原因。
    // 如果 Context 未被取消,返回 nil。
    Err() error

    // Value 返回與此 Context 關(guān)聯(lián)的鍵 key 對應(yīng)的值,如果沒有則返回 nil。
    // key 必須是可比較的類型,通常不應(yīng)是內(nèi)置的 string 類型或任何其他內(nèi)置類型,
    // 以避免不同包之間定義的鍵發(fā)生沖突。
    Value(key interface{}) interface{}
}
  • Done(): 這是實現(xiàn)取消信號的核心。下游的 goroutine 可以 select 這個 Done() channel,一旦它被關(guān)閉,就意味著上游發(fā)出了取消指令,goroutine 應(yīng)該停止當前工作并返回。
  • Err(): 當 Done() 關(guān)閉后,可以通過 Err() 獲取取消的原因。如果是超時取消,通常返回 context.DeadlineExceeded;如果是手動調(diào)用 cancel 函數(shù)取消,通常返回 context.Canceled。
  • Deadline(): 允許 goroutine 檢查是否還有足夠的時間來完成任務(wù)。
  • Value(): 用于傳遞請求范圍的數(shù)據(jù),如用戶 ID、追蹤 ID 等。注意:官方建議謹慎使用 Value,它主要用于傳遞貫穿整個請求調(diào)用鏈的元數(shù)據(jù),而不是用來傳遞可選參數(shù)。濫用 Value 會使代碼的依賴關(guān)系變得不明確。

創(chuàng)建和派生 Context

通常我們不直接實現(xiàn) Context 接口,而是使用 context 包提供的函數(shù)來創(chuàng)建和派生 Context:

  • context.Background(): 返回一個非 nil 的空 Context。它通常用在 main 函數(shù)、初始化以及測試代碼中,作為所有 Context 樹的根節(jié)點。它永遠不會被取消,沒有值,也沒有截止時間。
  • context.TODO(): 與 Background() 類似,也是一個空的 Context。它的用途是指示當前代碼還不清楚應(yīng)該使用哪個 Context,或者函數(shù)簽名后續(xù)可能會更新以接收 Context。它是一個臨時的占位符。
  • context.WithCancel(parent Context) (ctx Context, cancel CancelFunc): 創(chuàng)建一個新的 Context,它是 parent 的子節(jié)點。同時返回一個 cancel 函數(shù)。調(diào)用這個 cancel 函數(shù)會取消新的 ctx 及其所有子 Context。如果 parent 被取消,ctx 也會被取消。
  • context.WithDeadline(parent Context, d time.Time) (Context, CancelFunc): 創(chuàng)建一個帶有截止時間的 Context。當?shù)竭_時間 d 或 parent 被取消,或者調(diào)用返回的 cancel 函數(shù)時,ctx 會被取消。
  • context.WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc): 是 WithDeadline 的便利寫法,等價于 WithDeadline(parent, time.Now().Add(timeout))。
  • context.WithValue(parent Context, key, val interface{}) Context: 創(chuàng)建一個攜帶鍵值對的 Context。獲取值時,會先在當前 Context 查找,如果找不到,會遞歸地在父 Context 中查找。

這些派生函數(shù)創(chuàng)建了一個 Context 樹。取消操作會向下傳播,但值傳遞是向上查找的。

實際應(yīng)用場景示例

1.優(yōu)雅地取消長時間運行的任務(wù)

假設(shè)有一個函數(shù)需要執(zhí)行一項可能耗時較長的操作,我們希望能在外部取消它。

package main

import (
    "context"
    "fmt"
    "time"
)

// worker 模擬一個耗時任務(wù),它會監(jiān)聽 Context 的取消信號
func worker(ctx context.Context, id int) {
    fmt.Printf("Worker %d started\n", id)
    select {
    case <-time.After(5 * time.Second): // 模擬工作耗時
        fmt.Printf("Worker %d finished normally\n", id)
    case <-ctx.Done(): // 監(jiān)聽取消信號
        // Context 被取消,清理并退出
        fmt.Printf("Worker %d canceled: %v\n", id, ctx.Err())
    }
}

func main() {
    // 創(chuàng)建一個可以被取消的 Context
    ctx, cancel := context.WithCancel(context.Background())

    // 啟動一個 worker goroutine
    go worker(ctx, 1)

    // 等待一段時間
    time.Sleep(2 * time.Second)

    // 發(fā)出取消信號
    fmt.Println("Main: Sending cancellation signal...")
    cancel() // 調(diào)用 cancel 函數(shù)

    // 等待一小段時間,確保 worker 有時間響應(yīng)取消并打印信息
    time.Sleep(1 * time.Second)
    fmt.Println("Main: Finished")
}
$ go run main.go 
Worker 1 started
Main: Sending cancellation signal...
Worker 1 canceled: context canceled
Main: Finished

在這個例子中,main 函數(shù)創(chuàng)建了一個可取消的 Context 并傳遞給 worker。worker 使用 select 同時等待任務(wù)完成和 ctx.Done()。當 main 調(diào)用 cancel() 后,ctx.Done() 的 channel 會被關(guān)閉,worker 能夠捕獲到這個信號并提前退出。

2.設(shè)置 API 調(diào)用超時

在調(diào)用外部服務(wù)或數(shù)據(jù)庫時,設(shè)置超時是非常常見的需求。

package main

import (
    "context"
    "fmt"
    "net/http"
    "time"
)

func fetchURL(ctx context.Context, url string) (string, error) {
    // 使用 http.NewRequestWithContext 將 Context 與請求關(guān)聯(lián)
    // 這個例子實際上有些超前, NewRequestWithContext 在 go 1.13 中才被添加
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return "", fmt.Errorf("failed to create request: %w", err)
    }

    // 發(fā)送請求
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        // 如果是因為 Context 超時或取消導(dǎo)致的錯誤,err 會是 context.DeadlineExceeded 或 context.Canceled
        return "", fmt.Errorf("failed to fetch URL: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
    }

    // 這里簡化處理,實際應(yīng)用中會讀取 Body 內(nèi)容
    return fmt.Sprintf("Success: Status %d", resp.StatusCode), nil
}

func main() {
    // 創(chuàng)建一個帶有 1 秒超時的 Context
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel() // 良好的實踐:即使超時,也調(diào)用 cancel 釋放資源

    // 嘗試訪問一個響應(yīng)較慢的 URL (httpbin.org/delay/3 會延遲 3 秒響應(yīng))
    result, err := fetchURL(ctx, "https://httpbin.org/delay/3")

    if err != nil {
        fmt.Printf("Error fetching URL: %v\n", err)
        // 檢查錯誤是否由 Context 引起
        if ctx.Err() == context.DeadlineExceeded {
            fmt.Println("Reason: Context deadline exceeded")
        } else if ctx.Err() == context.Canceled {
            fmt.Println("Reason: Context canceled")
        }
    } else {
        fmt.Printf("Result: %s\n", result)
    }
}
$ go run main.go
# 實際上在 go 1.13 以上才能運行
Error fetching URL: failed to fetch URL: Get "https://httpbin.org/delay/3": context deadline exceeded
Reason: Context deadline exceeded

這里,我們使用 context.WithTimeout 創(chuàng)建了一個 1 秒后自動取消的 Context。http.NewRequestWithContext (Go 1.7 及以后版本提供) 將這個 Context 附加到 HTTP 請求上。http.DefaultClient.Do 會監(jiān)控這個 Context。如果請求在 1 秒內(nèi)沒有完成(包括連接、發(fā)送、接收響應(yīng)頭等階段),Do 方法會返回一個錯誤,并且這個錯誤可以通過 errors.Is(err, context.DeadlineExceeded) 來判斷是否是超時引起的。

3.傳遞請求范圍的數(shù)據(jù)

如官方博客文章示例,傳遞用戶 IP 地址。

package main

import (
    "context"
    "fmt"
    "net"
    "net/http"
    "time"
)

// 使用未導(dǎo)出的類型作為 key,防止命名沖突
type contextKey string

const userIPKey contextKey = "userIP"

// 將 IP 存入 Context
func NewContextWithUserIP(ctx context.Context, userIP net.IP) context.Context {
    return context.WithValue(ctx, userIPKey, userIP)
}

// 從 Context 取出 IP
func UserIPFromContext(ctx context.Context) (net.IP, bool) {
    ip, ok := ctx.Value(userIPKey).(net.IP)
    return ip, ok
}

// 模擬一個需要用戶 IP 的下游處理函數(shù)
func processRequest(ctx context.Context) {
    fmt.Println("Processing request...")
    if ip, ok := UserIPFromContext(ctx); ok {
        fmt.Printf("  User IP found in context: %s\n", ip.String())
    } else {
        fmt.Println("  User IP not found in context.")
    }
    // 模擬工作
    time.Sleep(50 * time.Millisecond)
    fmt.Println("Processing finished.")
}

// HTTP handler
func handleRequest(w http.ResponseWriter, r *http.Request) {
    // 嘗試從請求中解析 IP (簡化處理)
    ipStr, _, err := net.SplitHostPort(r.RemoteAddr)
    var userIP net.IP
    if err == nil {
        userIP = net.ParseIP(ipStr)
    }

    // 獲取請求的 Context (http.Request 自帶 Context)
    ctx := r.Context() // 通常這個 Context 已經(jīng)與請求的生命周期綁定

    // 如果獲取到 IP,將其添加到 Context 中
    if userIP != nil {
        ctx = NewContextWithUserIP(ctx, userIP)
    }

    // 調(diào)用下游處理函數(shù),傳遞帶有用戶 IP 的 Context
    processRequest(ctx)

    fmt.Fprintln(w, "Request processed.")
}

func main() {
    http.HandleFunc("/", handleRequest)
    fmt.Println("Starting server on :8080")
    // 注意:在實際生產(chǎn)中,需要配置 http.Server 并優(yōu)雅地關(guān)閉
    if err := http.ListenAndServe(":8080", nil); err != nil {
        fmt.Printf("Server failed: %v\n", err)
    }
}
$ go run main.go
Starting server on :8080
# 另一個終端 curl 127.0.0.1:8080
Processing request...
  User IP found in context: 127.0.0.1
Processing finished.

在這個例子中,HTTP handler 從請求中提取了客戶端 IP,并使用 context.WithValue 將其放入 Context 中。然后,它調(diào)用下游的 processRequest 函數(shù),并將這個增強后的 Context 傳遞下去。processRequest 可以通過 UserIPFromContext 函數(shù)安全地取出這個 IP 地址,而無需知道它是如何被添加到 Context 中的。這實現(xiàn)了跨函數(shù)邊界傳遞請求元數(shù)據(jù)的目的。

context 包的引入極大地提升了 Go 在構(gòu)建健壯、可維護的并發(fā)程序,特別是網(wǎng)絡(luò)服務(wù)器方面的能力。它成為了 Go 并發(fā)編程事實上的標準模式之一。

HTTP 追蹤:深入了解 HTTP 請求的生命周期

Go 1.7 引入了 net/http/httptrace 包,為開發(fā)者提供了一種細粒度觀察和測量 net/http 客戶端請求生命周期中各個階段耗時的方法。這對于性能分析、問題診斷(例如,是 DNS 查詢慢,還是建立連接慢,或是服務(wù)器響應(yīng)慢?)非常有幫助。

核心機制:httptrace.ClientTrace

httptrace 包的核心是 ClientTrace 結(jié)構(gòu)體。這個結(jié)構(gòu)體包含了一系列函數(shù)類型的字段,每個字段對應(yīng) HTTP 請求過程中的一個特定事件點(hook)。你可以為感興趣的事件點提供回調(diào)函數(shù)。

package httptrace

import (
 "context"
 "crypto/tls"
 "net"
 "time"
)

// ClientTrace 是一組可以注冊的回調(diào)函數(shù),用于追蹤 HTTP 客戶端請求期間發(fā)生的事件。
type ClientTrace struct {
 // GetConn 在獲取連接之前被調(diào)用。hostPort 是目標地址。
 GetConn func(hostPort string)
 // GotConn 在成功獲取連接后被調(diào)用。
 GotConn func(GotConnInfo)
 // PutIdleConn 在連接返回到空閑池時被調(diào)用。
 PutIdleConn func(err error)
 // GotFirstResponseByte 在收到響應(yīng)的第一個字節(jié)時被調(diào)用。
 GotFirstResponseByte func()
 // Got100Continue 在收到 "HTTP/1.1 100 Continue" 響應(yīng)時被調(diào)用。
 Got100Continue func()
 // Got1xxResponse 在收到以 1 開頭的非 100 狀態(tài)碼的響應(yīng)時被調(diào)用。
 Got1xxResponse func(code int, header string) error
 // DNSStart 在開始 DNS 查詢時被調(diào)用。
 DNSStart func(DNSStartInfo)
 // DNSDone 在 DNS 查詢結(jié)束后被調(diào)用。
 DNSDone func(DNSDoneInfo)
 // ConnectStart 在開始新的 TCP 連接時被調(diào)用。
 ConnectStart func(network, addr string)
 // ConnectDone 在新的 TCP 連接成功建立或失敗后被調(diào)用。
 ConnectDone func(network, addr string, err error)
 // WroteHeaderField 在 Transport 寫入 HTTP 請求頭中的每個鍵值對后被調(diào)用。
 WroteHeaderField func(key string, value []string)
 // WroteHeaders 在 Transport 成功寫入所有請求頭字段后被調(diào)用。
 WroteHeaders func()
 // Wait100Continue 在發(fā)送完請求頭后,如果請求包含 "Expect: 100-continue",
 // 在等待服務(wù)器的 "100 Continue" 響應(yīng)之前被調(diào)用。
 Wait100Continue func()
 // WroteRequest 在 Transport 成功寫入整個請求(包括主體)后被調(diào)用。
 WroteRequest func(WroteRequestInfo)
}

// GotConnInfo 包含關(guān)于已獲取連接的信息。
type GotConnInfo struct {
 Conn net.Conn // 獲取到的連接
 Reused bool    // 連接是否是從空閑池中復(fù)用的
 WasIdle bool   // 如果是復(fù)用連接,它在空閑池中時是否是空閑狀態(tài)
 IdleTime time.Duration // 如果是復(fù)用連接且是空閑狀態(tài),它空閑了多久
}

// ... 其他 Info 結(jié)構(gòu)體定義 ...

開發(fā)者可以創(chuàng)建一個 ClientTrace 實例,并為需要追蹤的事件(如 DNS 查詢、TCP 連接、TLS 握手、收到首字節(jié)等)設(shè)置回調(diào)函數(shù)。在這些回調(diào)函數(shù)中,通常會記錄事件發(fā)生的時間戳,以便后續(xù)計算各階段的耗時。

如何使用 httptrace

  1. 創(chuàng)建 ClientTrace 實例 :定義你關(guān)心的回調(diào)函數(shù)。
  2. 創(chuàng)建帶有 Trace 的 Context :使用 httptrace.WithClientTrace(parentCtx, trace) 將你的 ClientTrace 實例與一個 Context 關(guān)聯(lián)起來。
  3. 創(chuàng)建帶有該 Context 的 Request :使用 http.NewRequestWithContext(ctx, ...) 或 req = req.WithContext(ctx) 將上一步得到的 Context 附加到你的 http.Request 上。
  4. 執(zhí)行請求 :使用 http.Client(如 http.DefaultClient)的 Do 方法執(zhí)行這個請求。

在請求執(zhí)行過程中,net/http 包內(nèi)部會在相應(yīng)的事件點檢查 Request 關(guān)聯(lián)的 Context 中是否包含 ClientTrace,如果包含,則調(diào)用其中設(shè)置的回調(diào)函數(shù)。

代碼示例:測量 DNS 和 TCP 連接耗時

package main

import (
 "context"
 "fmt"
 "log"
 "net/http"
 "net/http/httptrace"
 "time"
)

func main() {
 url := "https://go.dev"
 req, _ := http.NewRequest("GET", url, nil)

 var start, connect, dns time.Time

 trace := &httptrace.ClientTrace{
  // DNS 查詢開始
  DNSStart: func(info httptrace.DNSStartInfo) {
   dns = time.Now()
   fmt.Println("DNS Start:", info.Host)
  },
  // DNS 查詢結(jié)束
  DNSDone: func(info httptrace.DNSDoneInfo) {
   fmt.Printf("DNS Done: %v, Err: %v, Duration: %v\n", info.Addrs, info.Err, time.Since(dns))
  },
  // TCP 連接開始 (包括 DNS 解析后的地址)
  ConnectStart: func(network, addr string) {
   connect = time.Now()
   fmt.Printf("Connect Start: Network=%s, Addr=%s\n", network, addr)
  },
  // TCP 連接結(jié)束
  ConnectDone: func(network, addr string, err error) {
   fmt.Printf("Connect Done: Network=%s, Addr=%s, Err: %v, Duration: %v\n", network, addr, err, time.Since(connect))
  },
  // 獲取到連接 (可能是新建的或復(fù)用的)
  GotConn: func(info httptrace.GotConnInfo) {
   start = time.Now() // 將獲取連接作為請求開始計時點
   fmt.Printf("Got Conn: Reused: %t, WasIdle: %t, IdleTime: %v\n", info.Reused, info.WasIdle, info.IdleTime)
  },
  // 收到響應(yīng)的第一個字節(jié)
  GotFirstResponseByte: func() {
   fmt.Printf("Time to First Byte: %v\n", time.Since(start))
  },
 }

 // 將 trace 關(guān)聯(lián)到 Context
 ctx := httptrace.WithClientTrace(context.Background(), trace)
 // 將帶有 trace 的 Context 附加到 Request
 req = req.WithContext(ctx)

 fmt.Println("Starting request to", url)
 // 執(zhí)行請求
 client := &http.Client{
  // 禁用 KeepAlives 可以確保每次都建立新連接,方便觀察 ConnectStart/Done
  // Transport: &http.Transport{DisableKeepAlives: true},
 }
 resp, err := client.Do(req)
 if err != nil {
  log.Fatalf("Request failed: %v", err)
 }
 defer resp.Body.Close()

 fmt.Printf("Request finished. Status: %s\n", resp.Status)
 // 注意:這里無法直接得到總耗時,總耗時需要自己記錄請求前后的時間戳來計算。
 // trace 主要用于分解內(nèi)部各階段的耗時。
}
$ go run main.go
Starting request to https://go.dev
DNS Start: go.dev
DNS Done: [{216.239.36.21 } {216.239.34.21 } {216.239.32.21 } {216.239.38.21 } {2001:4860:4802:36::15 } {2001:4860:4802:34::15 } {2001:4860:4802:32::15 } {2001:4860:4802:38::15 }], Err: <nil>, Duration: 311.409ms
Connect Start: Network=tcp, Addr=216.239.36.21:443
Connect Done: Network=tcp, Addr=216.239.36.21:443, Err: <nil>, Duration: 5.076ms
Got Conn: Reused: false, WasIdle: false, IdleTime: 0s
Time to First Byte: 373.383ms
Request finished. Status: 200 OK

運行這段代碼,你將看到控制臺輸出 DNS 查詢、TCP 連接、TLS 握手等階段的開始和結(jié)束信息,以及它們的耗時。這對于定位 HTTP 請求中的性能瓶頸非常有價值。

httptrace 包的引入,為 Go 開發(fā)者提供了一個強大的內(nèi)省工具,使得理解和優(yōu)化 HTTP 客戶端性能變得更加容易。

責任編輯:武曉燕 來源: Piper蛋窩
相關(guān)推薦

2025-04-18 08:07:12

2025-04-21 00:05:00

2025-04-21 08:00:56

2025-04-22 08:02:23

2025-04-23 08:02:40

2025-04-21 00:00:00

Go 開發(fā)Go 語言Go 1.9

2025-04-14 00:00:04

2025-04-24 09:01:46

2025-04-27 08:00:35

2025-04-27 00:00:01

Go 1.16Go 1.15接口

2025-04-30 09:02:46

2025-04-14 08:06:04

2025-04-15 08:00:53

2025-04-25 08:01:12

Go應(yīng)用程序部署

2025-04-29 08:03:18

2025-04-28 08:00:56

2025-04-11 08:02:38

2025-04-14 00:00:00

2025-04-10 08:03:18

Go 1rune? 類型類型推斷

2010-07-12 10:48:21

SQL Server數(shù)
點贊
收藏

51CTO技術(shù)棧公眾號