Search code examples
httpgogoogle-apiandroidpublisher

How to trace http.Client with httptrace in Go


Per this doc, we can trace http.Client with httptrace in this way

    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}

For google API client, here are the one wrapper codes

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
}

We want to apply httptrace to the http.Client argument of NewWithClient to do HTTP trace.

What we have tried

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)

However, it is failed to trace http.Client, There is no output of GotConn. Could someone help us to figure out the issue of the above codes?


Solution

    1. Requests from google/oauth2 are not traceable by httptrace. your ClientWrapper passed with context.WithValue will be ignored here, and oauth2 has it's own http.Client, it just use the Transport method of *http.Client from context.Value.

    2. Requests from androidpublisher can be traced by httptrace like this:

    ctx := httptrace.WithClientTrace(context.Background(), clientWrapperTrace)
    r, err := c.VerifyProduct(ctx, packageName, productID, token)
    
    1. If you just want to count the requests, i think overwrite the http.Client.Transport is a easy way.
    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)))
        // ....
    }