Skip to content

Instantly share code, notes, and snippets.

@lmas
Created January 1, 2018 13:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lmas/f163e2d4b25b48f87869c8a0be00c388 to your computer and use it in GitHub Desktop.
Save lmas/f163e2d4b25b48f87869c8a0be00c388 to your computer and use it in GitHub Desktop.
Simple API client with json parsing, gzip compression, ratelimit and oauth tokens
package main
import (
"compress/gzip"
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"github.com/juju/ratelimit"
"golang.org/x/oauth2/clientcredentials"
)
type Query map[string]string
type Conf struct {
UserAgent string
ClientTimeout time.Duration
RateLimit int64
OAuthConf clientcredentials.Config
}
type Client struct {
conf Conf
webClient *http.Client
rateLimiter *ratelimit.Bucket
}
func NewClient(conf Conf) *Client {
// NOTE: http.DefaultClient will be used to get the tokens, which means
// no timeouts are being used. It's fine being blocked forever I think,
// since we can't do anything without a valid token anyway.
// NOTE: also note that this returned client will automagically add the
// Authorization header when we have a token, but it won't show up in
// our requests until it's time to send it away!
wc := conf.OAuthConf.Client(context.Background())
// Now on the other hand, we want all our regular requests to have a timeout!
wc.Timeout = conf.ClientTimeout
client := &Client{
conf: conf,
webClient: wc,
rateLimiter: ratelimit.NewBucket(1*time.Second, conf.RateLimit),
}
return client
}
func (c *Client) Request(method, path string, query Query) (*http.Response, error) {
// TODO: add post body?
u, err := url.Parse(path)
if err != nil {
return nil, err
}
q := u.Query()
for k, v := range query {
if v != "" {
q.Add(k, v)
}
}
u.RawQuery = q.Encode()
req, err := http.NewRequest(method, u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Add("User-Agent", c.conf.UserAgent)
req.Header.Add("Accept-Encoding", "gzip")
c.rateLimiter.Wait(1)
resp, err := c.webClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("bad response: %s", resp.Status)
}
return resp, nil
}
func (c *Client) ParseJSON(resp *http.Response, model interface{}) error {
defer resp.Body.Close()
if model != nil {
b := resp.Body
if !resp.Uncompressed {
var err error
b, err = gzip.NewReader(resp.Body)
if err != nil {
return err
}
}
dec := json.NewDecoder(b)
return dec.Decode(model)
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment