> For the complete documentation index, see [llms.txt](https://wskdsgcf.gitbook.io/mastering-go-zh-cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wskdsgcf.gitbook.io/mastering-go-zh-cn/12.0/12.8.md).

# 12.8 追踪 HTTP

Go 支持用 `net/http/httptraces`标准包**追踪 HTTP**！这个包允许您追踪一个 HTTP 请求的解析。`net/http/httptraces`标准包的使用将在 `httpTrace.go` 中加以说明，它分为五部分进行讲解。

`httpTrace.go` 的第一部分如下：

```go
package main

import (
    "fmt"
    "io"
    "net/http"
    "net/http/httptrace"
    "os"
)
```

如您所料，您需要引入 `net/http/httptrace`包来追踪 HTTP。

`httpTrace.go` 的第二部分代码如下：

```go
func main() {
    if len(os.Args) != 2 {
        fmt.Println("Usage: URL\n")
        return
    }
    URL := os.Args[1]
    client := http.Client{}
```

在这部分，我们读取命令行参数并创建一个新的 `http.Client`变量。由于这是您第一次在执行中看到 `http.Client` 对象，所以关于它我们就多讲一些。`http.Client` 对象发送请求到服务器并获得一个响应。它的 `Transport` 字段允许您设置各种 HTTP 细节来替代默认值。

注意，您不应该在发布的软件中使用 `http.Client` 对象的默认值，因为这些值没有指定请求超时，它会影响程序的性能以及 goroutines的运行。而且，根据设计 `http.Client` 对象能够安全的用于并发程序。

`httpTrace.go` 的第三段代码如下：

```go
    req, _ := http.NewRequest("GET", URL, nil)
    trace := &httptrace.ClientTrace{
        GotFirstResponseByte: func() {
            fmt.Println("First response byte!")
        },
        GotConn: func(connInfo httptrace.GotConnInfo) {
            fmt.Printf("Got Conn: %+v\n", connInfo)
        },
        DNSDone: func(dnsInfo httptrace.DNSDoneInfo) {
            fmt.Printf("DNS Info: %+v\n", dnsInfo)
        },
        ConnectStart: func(network, addr string) {
            fmt.Println("Dial start")
        },
        ConnectDone: func(network, addr string, err error) {
            fmt.Println("Dial done")
        },
        WroteHeaders: func() {
            fmt.Println("Wrote headers")
        },
    }
```

上面这段就是全部追踪 HTTP 请求的代码了。`httptrace.ClientTrace` 对象定义了使我们感兴趣的事件。当一个事件发生时，相关代码就会被执行。您能够在 `net/http/httptrace` 包的文档中找到更多信息，关于支持的事件和它们的目的。

`httpTrace.go` 的第四部分如下：

```go
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
    fmt.Println("Requesting data from server!")
    _, err := http.DefaultTransport.RoundTrip(req)
    if err != nil {
        fmt.Println(err)
        return
    }
```

`httptrace.WithClientTrace()` 函数基于给定的父上下文返回一个新的上下文；而 `http.DefaultTransport.RoundTrip()` 方法包裹 `http.DefaultTransport.RoundTrip()` 目的是告诉它要追踪当前的请求。注意，Go HTTP 跟踪被设计为追踪单个 `http.Transport.RoundTrip` 的事件。然而，当服务一个单独的HTTP 请求时，由于您可能有多个 URL 重定向，所以您需要能够识别出当前的请求。

`httpTrace.go` 的剩余代码如下：

```go
    response, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
        return
    }
    io.Copy(os.Stdout, response.Body)
}
```

最后这部分是使用 `Do()` 执行实际的请求到 web 服务器，获取 HTTP 数据，并把它显示在屏幕上。

执行 `httpTrace.go` 将产生如下非常翔实的输出：

```
$ go run httpTrace.go http://localhost:8001/
Requesting data from server!
DNS Info: {Addrs: [{IP:::1 Zone:} {IP:127.0.0.1 Zone:}] Err:<nil> Coalesced:false}
Dial start
Dial done
Got Conn: {Conn:0xc42014200 Reused:false WasIdel:false IdleTime:0s}
Wrote headers
First response byte!
DNS Info: {Addrs: [{IP:::1 Zone:} {IP:127.0.0.1 Zone:}] Err:<nil> Coalesced:false}
Dial start
Dial done
Got Conn: {Conn:0xc420142008 Reused:false WasIdle:false IdleTime:0s}
Wrote headers
First response byte!
Serving: /
```

考虑到 `httpTrace.go` 打印从 HTTP 服务器返回的全部 HTML 数据，当您用一个真实的 web 服务测试时，您可能会得到太多输出，所以在这我们就使用 `www.go` 这个 web 服务器了。

> *如果您有时间看* `net/http/httptrace` *包的源码，在* [*http://golang.org/src/net/http/httptrace/trace.go*](http://golang.org/src/net/http/httptrace/trace.go)*，您会立马意识到* `net/http/httptrace` *是个相当低级的包，它使用* `context`*,* `reflect` *和* `internal/nettrace` *包实现它的功能。*
