如何在 Go 中使用 httptrace 跟踪 http.Client

How to trace http.Client with httptrace in Go

根据这个doc,我们可以用这种方式httptrace追踪http.Client

    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}

对于google API client,这是一个包装代码

func NewWithClient(jsonKey []byte, cli *http.Client) (*Client, error) {
    if cli == nil {
        return nil, fmt.Errorf("client is nil")
    }

    ctx := context.WithValue(context.Background(), oauth2.HTTPClient, cli)

    conf, err := google.JWTConfigFromJSON(jsonKey, androidpublisher.AndroidpublisherScope)
    if err != nil {
        return nil, err
    }

    service, err := androidpublisher.NewService(ctx, option.WithHTTPClient(conf.Client(ctx)))
    if err != nil {
        return nil, err
    }

    return &Client{service}, err
}

我们想将 httptrace 应用于 NewWithClienthttp.Client 参数以进行 HTTP 跟踪。

我们尝试过的

type TraceTransport struct {
}

var traceTransport = &TraceTransport{}

var trace = &httptrace.ClientTrace{
    GotConn: traceTransport.GotConn,
}

func (t *TraceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    return http.DefaultTransport.RoundTrip(req)
}

func (t *TraceTransport) GotConn(info httptrace.GotConnInfo) {
    fmt.Printf("Connection reused for %v \n", info.Reused)
}

type ClientWrapper struct {
    defaultClient *http.Client
}

var clientWrapperTrace = &httptrace.ClientTrace{GotConn: traceTransport.GotConn}

func (c *ClientWrapper) Do(req *http.Request) (*http.Response, error) {
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), clientWrapperTrace))
    return c.defaultClient.Do(req)
}

func NewClientTrace(jsonKey []byte) (*Client, error) {
    cli := &http.Client{
        Transport: traceTransport,
        Timeout:   time.Duration(10) * time.Second,
    }
    cliWrapper := &ClientWrapper{defaultClient: cli}
    ctx := context.WithValue(context.Background(), oauth2.HTTPClient, cliWrapper)

    conf, err := google.JWTConfigFromJSON(jsonKey, androidpublisher.AndroidpublisherScope)
    if err != nil {
        return nil, err
    }

    service, err := androidpublisher.NewService(ctx, option.WithHTTPClient(conf.Client(ctx)))
    if err != nil {
        return nil, err
    }

    return &Client{service}, err
}

type Client struct {
    service *androidpublisher.Service
}

func (c *Client) VerifyProduct(
    ctx context.Context,
    packageName string,
    productID string,
    token string,
) (*androidpublisher.ProductPurchase, error) {
    ps := androidpublisher.NewPurchasesProductsService(c.service)
    result, err := ps.Get(packageName, productID, token).Context(ctx).Do()

    return result, err
}

// test codes
  c, err := NewClientTrace([]byte(privateKey)) 
  if err != nil {
    return
  }

  packageName := "package.name"
  productID := "product_id"
  token := "xxxxx"
  r, err := c.VerifyProduct(context.Background(), packageName, productID, token)

但是追踪失败http.Client,没有GotConn的输出。有人可以帮助我们找出上述代码的问题吗?

  1. 来自 google/oauth2 的请求无法被 httptrace 追踪。你用 context.WithValue 传递的 ClientWrapper 将被忽略 here, and oauth2 has it's own http.Client,它只是使用 context.Value.

    *http.ClientTransport 方法
  2. 来自androidpublisher的请求可以通过httptrace这样追踪:

ctx := httptrace.WithClientTrace(context.Background(), clientWrapperTrace)
r, err := c.VerifyProduct(ctx, packageName, productID, token)
  1. 如果您只想计算请求数,我认为覆盖 http.Client.Transport 是一种简单的方法。
type TraceTransport struct {
}

func (t *TraceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    fmt.Printf("RoundTrip hook %v\n", req.URL)
    return http.DefaultTransport.RoundTrip(req)
}

func NewClientTrace(jsonKey []byte) (*Client, error) {
    cli := &http.Client{Transport: &TraceTransport{}}
    ctx := context.WithValue(context.Background(), oauth2.HTTPClient, cli)

    // ...
    service, err := androidpublisher.NewService(ctx, option.WithHTTPClient(conf.Client(ctx)))
    // ....
}