1

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?

zangw
  • 43,869
  • 19
  • 177
  • 214
  • What does "it is failed to trace" mean? `GotConn()` never gets called? How are you using the `Client` returned by `NewClientTrace()`? – icza Oct 21 '21 at 08:11
  • @icza, there is no output of `GotConn()`. Also some test codes were updated to question – zangw Oct 21 '21 at 08:31
  • `fmt.Printf()` logs to standard output. Are you sure that is directed to where you expect it? `log.Printf()` logs to standard error, try that too. – icza Oct 21 '21 at 08:34
  • @icza, the client code is done in test code `func TestClientTrace(t *testing.T)`. the `fmt.Printf()` could be output in console – zangw Oct 21 '21 at 08:44
  • Can you show what `Client` is? And what's inside `c.VerifyProduct()`? – icza Oct 21 '21 at 08:46
  • @icza, Sorry for missing those codes of Client, question has been updated – zangw Oct 21 '21 at 08:50
  • You want to trace requests from oauth or androidpublisher? – emptyhua Oct 21 '21 at 12:47
  • @emptyhua, yes, we want to trace both the oauth and androidpublisher, and count the call number of google API call. – zangw Oct 21 '21 at 12:50

1 Answers1

2
  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)))
    // ....
}
emptyhua
  • 6,634
  • 10
  • 11