> Source: https://www.ymotongpoo.com/works/goblog-ja/http-tracing/


# HTTPトレーシングの紹介

[Introducing HTTP Tracing](https://go.dev/blog/http-tracing) by Jaana Burcu Dogan

## はじめに

Go 1.7で、HTTPクライアントリクエストのライフサイクル全体を通してきめ細かい情報を収集する機能である
HTTPトレーシングを導入しました。HTTPトレーシングのサポートは
[`net/http/httptrace`](https://pkg.go.dev/net/http/httptrace) パッケージによって提供されています。
収集した情報は、レイテンシの問題のデバッグ、サービスの監視、適応型システムの構築など、
さまざまな用途に利用できます。

## HTTPイベント

`httptrace` パッケージは、HTTPラウンドトリップの間にさまざまなイベントに関する情報を収集するための、
多数のフックを提供します。これらのイベントには次のものが含まれます。

* コネクションの作成
* コネクションの再利用
* DNSルックアップ
* リクエストのワイヤーへの書き込み
* レスポンスの読み込み

## イベントのトレーシング

フック関数を含む [`*httptrace.ClientTrace`](https://pkg.go.dev/net/http/httptrace#ClientTrace) を
リクエストの [`context.Context`](https://pkg.go.dev/context) に格納することで、HTTPトレーシングを
有効にできます。さまざまな [`http.RoundTripper`](https://pkg.go.dev/net/http#RoundTripper) の実装は、
コンテキストの中にある `*httptrace.ClientTrace` を探し、関連するフック関数を呼び出すことで、
内部のイベントを報告します。

トレーシングはリクエストのコンテキストにスコープされるので、利用者はリクエストを開始する前に
`*httptrace.ClientTrace` をリクエストのコンテキストに格納しておく必要があります。

```go
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でのトレーシング

このトレーシングの仕組みは、単一の `http.Transport.RoundTrip` のライフサイクルにおけるイベントを
トレースするよう設計されています。しかし、クライアントは1つのHTTPリクエストを完了するために
複数回のラウンドトリップを行うことがあります。たとえば、URLリダイレクトが発生する場合、
クライアントがHTTPリダイレクトに追従するたびに複数のリクエストが発行され、登録済みのフックは
その回数分だけ呼び出されます。利用者は、このようなイベントを `http.Client` のレベルで認識する
責任を負います。以下のプログラムは、`http.RoundTripper` のラッパーを使って現在のリクエストを
識別しています。

```go
package main

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

// transport は処理中のリクエストを追跡し、HTTPトレーシングイベントを報告する
// フックを実装する http.RoundTripper です。
type transport struct {
    current *http.Request
}

// RoundTrip は現在のリクエストを追跡するために http.DefaultTransport.RoundTrip を
// ラップします。
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
    t.current = req
    return http.DefaultTransport.RoundTrip(req)
}

// GotConn は、現在のリクエストに対してコネクションが以前に使用されたことが
// あるかどうかを出力します。
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](http://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の両方のリクエストのトレーシングを
サポートしています。

独自の `http.RoundTripper` の実装を作成する場合は、リクエストのコンテキストに
`*httptest.ClientTrace` があるかを確認し、イベントが発生した際に関連するフックを呼び出すことで、
トレーシングをサポートできます。

## まとめ

HTTPトレーシングは、HTTPリクエストのレイテンシのデバッグや、送信トラフィックのネットワーク
デバッグ用ツールの作成に関心がある人にとって、Goへの価値ある追加機能です。この新機能を導入する
ことで、[httpstat](https://github.com/davecheney/httpstat) のような、HTTPのデバッグ、ベンチマーク、
可視化のためのツールがコミュニティから生まれることを期待しています。

By Jaana Burcu Dogan

