# 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` *包实现它的功能。*


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wskdsgcf.gitbook.io/mastering-go-zh-cn/12.0/12.8.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
