# twitchclient

[![GoDoc](http://godoc.internal.justin.tv/code.justin.tv/foundation/twitchclient?status.svg)](http://godoc.internal.justin.tv/code.justin.tv/foundation/twitchclient)

`twitchclient` enables creation of production-ready HTTP clients for Twitch services. This library should be used to build clients for services built with [twitchserver](https://git-aws.internal.justin.tv/foundation/twitchserver).

## Configured `*http.Client`

Use the constructor `twitchclient.NewHTTPClient(clientConf)` to build an `*http.Client` alerady configured with RoundTrippers that add:

* Tracer Stats (see lifecycle metrics below).
* Request Timing Stats (request duration from the client).
* Headers for `twitchserver` from `ReqOpts`: "Twitch-Authorization", "Twitch-Client-Row-ID", "Twitch-Client-ID", etc.
* [XRay](https://git-aws.internal.justin.tv/foundation/xray)

Basic Usage:

```go
// Build http.Client
httpClient := twitchclient.NewHTTPClient(twitchclient.ClientConf{Stats: stats})

// Configure request with options
ctx := context.Background()
ctx = twitchclient.WithReqOpts(ctx, twitchclient.ReqOpts{
    StatName: "statname",
    StatSampleRate: 0.2,
})
req, _ := http.NewRequest("GET", host+"/path", body)
req = req.WithContext(ctx)

// Do request
httpResp, err := httpClient.Do(req)
```

This low level client is used to provide Twitch integration to other libraries like [Twirp](https://github.com/twitchtv/twirp).
If your service is a JSON API, you should consider wrapping the `*http.Client` with a `JSONClient` to safely handle JSON requests.

## JSONClient

If your service is a JSON API, the easiest and safest way to build a client is with `JSONClient`.

For example, given a service to load, create and delete emotes, a fully working client would look like this:

```go
package emotesclient

import (
	"context"
	"code.justin.tv/foundation/twitchclient"
)

type Emote struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

type Client struct {
	twitchclient.JSONClient
}

func NewClient(host string, conf twitchclient.ClientConf) *Client {
	httpClient := twitchclient.NewHTTPClient(conf)
	jsonClient := twitchclient.NewJSONClient(host, httpClient)
	return &Client{jsonClient}
}

func (c *Client) GetEmote(ctx context.Context, id string) (*Emote, error) {
	path := twitchclient.Pathf("/emotes/%s", id) // sanitized path
	emote := Emote{}
	err := c.Get(ctx, path, nil, &emote)
	return emote, err
}

func (c *Client) PutEmote(ctx context.Context, emote *Emote) error {
	path := twitchclient.Pathf("/emotes")
	return c.Put(ctx, path, emote, nil)
}

func (c *Client) DeleteEmote(ctx context.Context, id string) error {
	path := twitchclient.Pathf("/emotes/%s", id)
	return c.Delete(ctx, path)
}
```

JSONClient has methods to `Get`, `Post`, `Put` and `Delete` with JSON serialization for body and responses.
It also has a lower level method `Do` that accepts different combinations of methods, headers and query parameters.


## Legacy `Client` and `DoJSON` method

Most exising clients in Twitch are written with the legacy `twitchclient.Client` and then making requests with the methods `.Do` or `.DoJSON`.
Usage of this client has been replaced with `JSONClient`, which has a few usability and security advantages (see PR https://git-aws.internal.justin.tv/foundation/twitchclient/pull/39).

Updating to use the new JSONClient is easy, and will mostly require removing redundant duplicated code.


## Request lifecycle metrics

`twitchclient` offers detailed tracing throughout the HTTP request life cycle.

The stats are powered by `httptrace` and can be found as graphite series.
By default, the non-clustered `statter` provided by `common/config` is used.

### Counters

* `dial.$host.failure`: How many times a dial fails.
* `req_write.$host.failure`: How many times a request or body fails to be written. An example is when the body's io.Reader returns an error.
* `get_connection.$host.reused`: How many connections were reused, as opposed to being created. A low number likely means the connection pool should be larger.
* `get_connection.$host.was_idle`: How many connections were obtained from the idle pool.
* `tls_handshake.$host.failure`: How many times a TLS handshake fails.
* `$dns-prefix.$host.coalesced`: How many DNS lookups were performed concurrently, requiring only a single DNS resolution.
* `$dns-prefix.$host.failure`: How many DNS lookups were not successful.

### Timers

* `get_connection.$host.idle_time`: If a connection was idle when it was retrieved, how long it spent while idle. If this number is too high, you may be able to decrease the size of your connection pool.
* `get_connection.$host.success`: How long a request spends creating or retreiving an idle connection.
* `dial.$host.success`: How long a request spends dialing a host.
* `tls_handshake.$host.success`: How long a request spends performing a TLS handshake.
* `$dns-prefix.$host.success`: How long a request spends performing DNS lookups.
