github.com/jogo/docker@v1.7.0-rc1/pkg/plugins/client.go (about) 1 package plugins 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net" 9 "net/http" 10 "strings" 11 "time" 12 13 "github.com/Sirupsen/logrus" 14 ) 15 16 const ( 17 versionMimetype = "application/vnd.docker.plugins.v1+json" 18 defaultTimeOut = 30 19 ) 20 21 func NewClient(addr string) *Client { 22 tr := &http.Transport{} 23 protoAndAddr := strings.Split(addr, "://") 24 configureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1]) 25 return &Client{&http.Client{Transport: tr}, protoAndAddr[1]} 26 } 27 28 type Client struct { 29 http *http.Client 30 addr string 31 } 32 33 func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error { 34 return c.callWithRetry(serviceMethod, args, ret, true) 35 } 36 37 func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret interface{}, retry bool) error { 38 var buf bytes.Buffer 39 if err := json.NewEncoder(&buf).Encode(args); err != nil { 40 return err 41 } 42 43 req, err := http.NewRequest("POST", "/"+serviceMethod, &buf) 44 if err != nil { 45 return err 46 } 47 req.Header.Add("Accept", versionMimetype) 48 req.URL.Scheme = "http" 49 req.URL.Host = c.addr 50 51 var retries int 52 start := time.Now() 53 54 for { 55 resp, err := c.http.Do(req) 56 if err != nil { 57 if !retry { 58 return err 59 } 60 61 timeOff := backoff(retries) 62 if abort(start, timeOff) { 63 return err 64 } 65 retries++ 66 logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff) 67 time.Sleep(timeOff) 68 continue 69 } 70 71 if resp.StatusCode != http.StatusOK { 72 remoteErr, err := ioutil.ReadAll(resp.Body) 73 if err != nil { 74 return nil 75 } 76 return fmt.Errorf("Plugin Error: %s", remoteErr) 77 } 78 79 return json.NewDecoder(resp.Body).Decode(&ret) 80 } 81 } 82 83 func backoff(retries int) time.Duration { 84 b, max := 1, defaultTimeOut 85 for b < max && retries > 0 { 86 b *= 2 87 retries-- 88 } 89 if b > max { 90 b = max 91 } 92 return time.Duration(b) * time.Second 93 } 94 95 func abort(start time.Time, timeOff time.Duration) bool { 96 return timeOff+time.Since(start) > time.Duration(defaultTimeOut)*time.Second 97 } 98 99 func configureTCPTransport(tr *http.Transport, proto, addr string) { 100 // Why 32? See https://github.com/docker/docker/pull/8035. 101 timeout := 32 * time.Second 102 if proto == "unix" { 103 // No need for compression in local communications. 104 tr.DisableCompression = true 105 tr.Dial = func(_, _ string) (net.Conn, error) { 106 return net.DialTimeout(proto, addr, timeout) 107 } 108 } else { 109 tr.Proxy = http.ProxyFromEnvironment 110 tr.Dial = (&net.Dialer{Timeout: timeout}).Dial 111 } 112 }