func doTimeout(cli *Client, req *http.Request, timeout time.Duration) (*http.Response, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	reqCancel := make(chan struct{})
	req.Cancel = reqCancel
	type response struct {
		resp *http.Response
		err  error
	}
	c := make(chan response, 1)
	go func() {
		var r response
		r.resp, r.err = cli.cli.Do(req)
		c <- r
	}()
	select {
	case <-ctx.Done():
		close(reqCancel)
		<-c
		return nil, ctx.Err()
	case r := <-c:
		return r.resp, r.err
	}
}
adapted from https://blog.golang.org/context httpDo func
package libkb
import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"runtime"
	"strings"
	"sync"
	"time"
	"golang.org/x/net/context"
	"github.com/PuerkitoBio/goquery"
	jsonw "github.com/keybase/go-jsonw"
)
type BaseAPIEngine struct {
	Contextified
	config    *ClientConfig
	clientsMu sync.Mutex
	clients   map[int]*Client
}
type InternalAPIEngine struct {
	BaseAPIEngine
}
type ExternalAPIEngine struct {
	BaseAPIEngine
}
type Requester interface {
	Contextifier
	fixHeaders(arg APIArg, req *http.Request)
	getCli(needSession bool) *Client
	consumeHeaders(resp *http.Response) error
	isExternal() bool
}
func NewInternalAPIEngine(g *GlobalContext) (*InternalAPIEngine, error) {
	cliConfig, err := g.Env.GenClientConfigForInternalAPI()
	if err != nil {
		return nil, err
	}
	i := &InternalAPIEngine{
		BaseAPIEngine{
			config:       cliConfig,
			clients:      make(map[int]*Client),
			Contextified: NewContextified(g),
		},
	}
	return i, nil
}
func NewAPIEngines(g *GlobalContext) (*InternalAPIEngine, *ExternalAPIEngine, error) {
	i, err := NewInternalAPIEngine(g)
	if err != nil {
		return nil, nil, err
	}
	scraperConfig, err := g.Env.GenClientConfigForScrapers()
	x := &ExternalAPIEngine{
		BaseAPIEngine{
			config:       scraperConfig,
			clients:      make(map[int]*Client),
			Contextified: NewContextified(g),
		},
	}
	return i, x, nil
}
type APIStatus struct {
	Code int    `json:"code"`
	Name string `json:"name"`
}
type APIError struct {
	Msg  string
	Code int
}
func NewAPIErrorFromHTTPResponse(r *http.Response) *APIError {
	return &APIError{r.Status, r.StatusCode}
}
func (a *APIError) Error() string {
	if len(a.Msg) > 0 {
		return a.Msg
	} else if a.Code > 0 {
		return fmt.Sprintf("Error HTTP status %d", a.Code)
	} else {
		return "Generic API error"
	}
}
func (api *BaseAPIEngine) getCli(cookied bool) (ret *Client) {
	key := 0
	if cookied {
		key |= 1
	}
	api.clientsMu.Lock()
	client, found := api.clients[key]
	if !found {
		api.G().Log.Debug("| Cli wasn't found; remaking for cookied=%v", cookied)
		client = NewClient(api.G().Env, api.config, cookied)
		api.clients[key] = client
	}
	api.clientsMu.Unlock()
	return client
}
func (api *BaseAPIEngine) PrepareGet(url url.URL, arg APIArg) (*http.Request, error) {
	url.RawQuery = arg.getHTTPArgs().Encode()
	ruri := url.String()
	api.G().Log.Debug("+ API GET request to %s", ruri)
	return http.NewRequest("GET", ruri, nil)
}
func (api *BaseAPIEngine) PreparePost(url url.URL, arg APIArg, sendJSON bool) (*http.Request, error) {
	ruri := url.String()
	api.G().Log.Debug(fmt.Sprintf("+ API Post request to %s", ruri))
	var body io.Reader
	if sendJSON {
		if len(arg.getHTTPArgs()) > 0 {
			panic("PreparePost: sending JSON, but http args exist and they will be ignored. Fix your APIArg.")
		}
		jsonString, err := json.Marshal(arg.JSONPayload)
		if err != nil {
			return nil, err
		}
		body = ioutil.NopCloser(strings.NewReader(string(jsonString)))
	} else {
		body = ioutil.NopCloser(strings.NewReader(arg.getHTTPArgs().Encode()))
	}
	req, err := http.NewRequest("POST", ruri, body)
	if err != nil {
		return nil, err
	}
	var typ string
	if sendJSON {
		typ = "application/json"
	} else {
		typ = "application/x-www-form-urlencoded; charset=utf-8"
	}
	req.Header.Add("Content-Type", typ)
	return req, nil
}
func doRequestShared(api Requester, arg APIArg, req *http.Request, wantJSONRes bool) (
	_ *http.Response, jw *jsonw.Wrapper, err error) {
	if !api.G().Env.GetTorMode().UseSession() && arg.NeedSession {
		err = TorSessionRequiredError{}
		return
	}
	dbg := func(s string) {
		api.G().Log.Debug(fmt.Sprintf("| doRequestShared(%s) for %s", s, arg.Endpoint))
	}
	dbg("fixHeaders")
	api.fixHeaders(arg, req)
	dbg("getCli")
	cli := api.getCli(arg.NeedSession)
	timerType := TimerAPI
	if api.isExternal() {
		timerType = TimerXAPI
	}
	if api.G().Env.GetAPIDump() {
		jpStr, _ := json.MarshalIndent(arg.JSONPayload, "", "  ")
		argStr, _ := json.MarshalIndent(arg.getHTTPArgs(), "", "  ")
		api.G().Log.Debug(fmt.Sprintf("| full request: json:%s querystring:%s", jpStr, argStr))
	}
	timer := api.G().Timers.Start(timerType)
	dbg("Do")
	internalResp, err := doRetry(api, arg, cli, req)
	dbg("Done")
	defer func() {
		if internalResp != nil && err != nil {
			DiscardAndCloseBody(internalResp)
		}
	}()
	timer.Report(req.Method + " " + arg.Endpoint)
	if err != nil {
		return nil, nil, APINetError{err: err}
	}
	api.G().Log.Debug(fmt.Sprintf("| Result is: %s", internalResp.Status))
	err = api.consumeHeaders(internalResp)
	if err != nil {
		return nil, nil, err
	}
	err = checkHTTPStatus(arg, internalResp)
	if err != nil {
		return nil, nil, err
	}
	if wantJSONRes {
		decoder := json.NewDecoder(internalResp.Body)
		var obj interface{}
		decoder.UseNumber()
		err = decoder.Decode(&obj)
		if err != nil {
			err = fmt.Errorf("Error in parsing JSON reply from server: %s", err)
			return nil, nil, err
		}
		jw = jsonw.NewWrapper(obj)
		if api.G().Env.GetAPIDump() {
			b, _ := json.MarshalIndent(obj, "", "  ")
			api.G().Log.Debug(fmt.Sprintf("| full reply: %s", b))
		}
	}
	return internalResp, jw, nil
}
func doRetry(g Contextifier, arg APIArg, cli *Client, req *http.Request) (*http.Response, error) {
	if arg.InitialTimeout == 0 && arg.RetryCount == 0 {
		return cli.cli.Do(req)
	}
	timeout := cli.cli.Timeout
	if arg.InitialTimeout != 0 {
		timeout = arg.InitialTimeout
	}
	retries := 1
	if arg.RetryCount > 1 {
		retries = arg.RetryCount
	}
	multiplier := 1.0
	if arg.RetryMultiplier != 0.0 {
		multiplier = arg.RetryMultiplier
	}
	var lastErr error
	for i := 0; i < retries; i++ {
		if i > 0 {
			g.G().Log.Debug("retry attempt %d of %d for %s", i, retries, arg.Endpoint)
		}
		resp, err := doTimeout(cli, req, timeout)
		if err == nil {
			return resp, nil
		}
		lastErr = err
		timeout = time.Duration(float64(timeout) * multiplier)
	}
	return nil, fmt.Errorf("doRetry failed, attempts: %d, timeout %s, last err: %s", retries, timeout, lastErr)
}