Go 博客

引入 HTTP Tracing

Jaana Burcu Dogan
2016 年 10 月 4 日

简介

在 Go 1.7 中,我们引入了 HTTP tracing,它是一种在 HTTP 客户端请求的整个生命周期中收集细粒度信息的机制。HTTP tracing 的支持由 net/http/httptrace 包提供。收集到的信息可用于调试延迟问题、服务监控、编写自适应系统等。

HTTP 事件

httptrace 包提供了许多钩子,用于在 HTTP 往返过程中收集有关各种事件的信息。这些事件包括

  • 连接创建
  • 连接复用
  • DNS 查找
  • 将请求写入网络
  • 读取响应

Tracing 事件

您可以通过将包含钩子函数的 *httptrace.ClientTrace 放入请求的 context.Context 中来启用 HTTP tracing。各种 http.RoundTripper 实现通过查找 context 中的 *httptrace.ClientTrace 并调用相关的钩子函数来报告内部事件。

tracing 作用域限定在请求的 context 中,用户应在发起请求之前将 *httptrace.ClientTrace 放入请求的 context 中。

    req, _ := http.NewRequest("GET", "http://example.com", nil)
    trace := &httptrace.ClientTrace{
        DNSDone: func(dnsInfo httptrace.DNSDoneInfo) {
            fmt.Printf("DNS Info: %+v\n", dnsInfo)
        },
        GotConn: func(connInfo httptrace.GotConnInfo) {
            fmt.Printf("Got Conn: %+v\n", connInfo)
        },
    }
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
    if _, err := http.DefaultTransport.RoundTrip(req); err != nil {
        log.Fatal(err)
    }

在往返过程中,http.DefaultTransport 会在事件发生时调用每个钩子。上面的程序会在 DNS 查找完成后立即打印 DNS 信息。类似地,当与请求的主机建立连接时,它会打印连接信息。

使用 http.Client 进行 tracing

tracing 机制旨在追踪单个 http.Transport.RoundTrip 生命周期中的事件。然而,客户端可能需要进行多次往返才能完成一个 HTTP 请求。例如,在 URL 重定向的情况下,注册的钩子会随着客户端跟随 HTTP 重定向而多次被调用,从而发起多个请求。用户需要负责在 http.Client 级别识别此类事件。下面的程序通过使用 http.RoundTripper 包装器来识别当前请求。

package main

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

// transport is an http.RoundTripper that keeps track of the in-flight
// request and implements hooks to report HTTP tracing events.
type transport struct {
    current *http.Request
}

// RoundTrip wraps http.DefaultTransport.RoundTrip to keep track
// of the current request.
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
    t.current = req
    return http.DefaultTransport.RoundTrip(req)
}

// GotConn prints whether the connection has been used previously
// for the current request.
func (t *transport) GotConn(info httptrace.GotConnInfo) {
    fmt.Printf("Connection reused for %v? %v\n", t.current.URL, info.Reused)
}

func main() {
    t := &transport{}

    req, _ := http.NewRequest("GET", "https://google.com", nil)
    trace := &httptrace.ClientTrace{
        GotConn: t.GotConn,
    }
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

    client := &http.Client{Transport: t}
    if _, err := client.Do(req); err != nil {
        log.Fatal(err)
    }
}

程序将跟随 google.com 重定向至 www.google.com 并输出

Connection reused for https://google.com? false
Connection reused for https://www.google.com/? false

net/http 包中的 Transport 支持对 HTTP/1 和 HTTP/2 请求进行 tracing。

如果您是自定义 http.RoundTripper 实现的作者,您可以通过检查请求 context 中的 *httptest.ClientTrace 并在事件发生时调用相关钩子来支持 tracing。

结论

HTTP tracing 对于那些有兴趣调试 HTTP 请求延迟和编写出站流量网络调试工具的人来说是 Go 的一个宝贵补充。通过启用此新功能,我们希望看到社区涌现出 HTTP 调试、基准测试和可视化工具,例如 httpstat

下一篇文章:Go 的七年
上一篇文章:使用 Subtests 和 Sub-benchmarks
博客索引