github.com/ld86/docker@v1.7.1-rc3/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 defer resp.Body.Close() 72 if resp.StatusCode != http.StatusOK { 73 remoteErr, err := ioutil.ReadAll(resp.Body) 74 if err != nil { 75 return nil 76 } 77 return fmt.Errorf("Plugin Error: %s", remoteErr) 78 } 79 80 return json.NewDecoder(resp.Body).Decode(&ret) 81 } 82 } 83 84 func backoff(retries int) time.Duration { 85 b, max := 1, defaultTimeOut 86 for b < max && retries > 0 { 87 b *= 2 88 retries-- 89 } 90 if b > max { 91 b = max 92 } 93 return time.Duration(b) * time.Second 94 } 95 96 func abort(start time.Time, timeOff time.Duration) bool { 97 return timeOff+time.Since(start) > time.Duration(defaultTimeOut)*time.Second 98 } 99 100 func configureTCPTransport(tr *http.Transport, proto, addr string) { 101 // Why 32? See https://github.com/docker/docker/pull/8035. 102 timeout := 32 * time.Second 103 if proto == "unix" { 104 // No need for compression in local communications. 105 tr.DisableCompression = true 106 tr.Dial = func(_, _ string) (net.Conn, error) { 107 return net.DialTimeout(proto, addr, timeout) 108 } 109 } else { 110 tr.Proxy = http.ProxyFromEnvironment 111 tr.Dial = (&net.Dialer{Timeout: timeout}).Dial 112 } 113 }