github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/pkg/plugins/client.go (about) 1 package plugins 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "net/url" 10 "time" 11 12 "github.com/Sirupsen/logrus" 13 "github.com/docker/docker/pkg/plugins/transport" 14 "github.com/docker/go-connections/sockets" 15 "github.com/docker/go-connections/tlsconfig" 16 ) 17 18 const ( 19 defaultTimeOut = 30 20 ) 21 22 // NewClient creates a new plugin client (http). 23 func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) { 24 tr := &http.Transport{} 25 26 if tlsConfig != nil { 27 c, err := tlsconfig.Client(*tlsConfig) 28 if err != nil { 29 return nil, err 30 } 31 tr.TLSClientConfig = c 32 } 33 34 u, err := url.Parse(addr) 35 if err != nil { 36 return nil, err 37 } 38 socket := u.Host 39 if socket == "" { 40 // valid local socket addresses have the host empty. 41 socket = u.Path 42 } 43 if err := sockets.ConfigureTransport(tr, u.Scheme, socket); err != nil { 44 return nil, err 45 } 46 scheme := httpScheme(u) 47 48 clientTransport := transport.NewHTTPTransport(tr, scheme, socket) 49 return NewClientWithTransport(clientTransport), nil 50 } 51 52 // NewClientWithTransport creates a new plugin client with a given transport. 53 func NewClientWithTransport(tr transport.Transport) *Client { 54 return &Client{ 55 http: &http.Client{ 56 Transport: tr, 57 }, 58 requestFactory: tr, 59 } 60 } 61 62 // Client represents a plugin client. 63 type Client struct { 64 http *http.Client // http client to use 65 requestFactory transport.RequestFactory 66 } 67 68 // Call calls the specified method with the specified arguments for the plugin. 69 // It will retry for 30 seconds if a failure occurs when calling. 70 func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error { 71 var buf bytes.Buffer 72 if args != nil { 73 if err := json.NewEncoder(&buf).Encode(args); err != nil { 74 return err 75 } 76 } 77 body, err := c.callWithRetry(serviceMethod, &buf, true) 78 if err != nil { 79 return err 80 } 81 defer body.Close() 82 if ret != nil { 83 if err := json.NewDecoder(body).Decode(&ret); err != nil { 84 logrus.Errorf("%s: error reading plugin resp: %v", serviceMethod, err) 85 return err 86 } 87 } 88 return nil 89 } 90 91 // Stream calls the specified method with the specified arguments for the plugin and returns the response body 92 func (c *Client) Stream(serviceMethod string, args interface{}) (io.ReadCloser, error) { 93 var buf bytes.Buffer 94 if err := json.NewEncoder(&buf).Encode(args); err != nil { 95 return nil, err 96 } 97 return c.callWithRetry(serviceMethod, &buf, true) 98 } 99 100 // SendFile calls the specified method, and passes through the IO stream 101 func (c *Client) SendFile(serviceMethod string, data io.Reader, ret interface{}) error { 102 body, err := c.callWithRetry(serviceMethod, data, true) 103 if err != nil { 104 return err 105 } 106 defer body.Close() 107 if err := json.NewDecoder(body).Decode(&ret); err != nil { 108 logrus.Errorf("%s: error reading plugin resp: %v", serviceMethod, err) 109 return err 110 } 111 return nil 112 } 113 114 func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) (io.ReadCloser, error) { 115 req, err := c.requestFactory.NewRequest(serviceMethod, data) 116 if err != nil { 117 return nil, err 118 } 119 120 var retries int 121 start := time.Now() 122 123 for { 124 resp, err := c.http.Do(req) 125 if err != nil { 126 if !retry { 127 return nil, err 128 } 129 130 timeOff := backoff(retries) 131 if abort(start, timeOff) { 132 return nil, err 133 } 134 retries++ 135 logrus.Warnf("Unable to connect to plugin: %s%s: %v, retrying in %v", req.URL.Host, req.URL.Path, err, timeOff) 136 time.Sleep(timeOff) 137 continue 138 } 139 140 if resp.StatusCode != http.StatusOK { 141 b, err := ioutil.ReadAll(resp.Body) 142 resp.Body.Close() 143 if err != nil { 144 return nil, &statusError{resp.StatusCode, serviceMethod, err.Error()} 145 } 146 147 // Plugins' Response(s) should have an Err field indicating what went 148 // wrong. Try to unmarshal into ResponseErr. Otherwise fallback to just 149 // return the string(body) 150 type responseErr struct { 151 Err string 152 } 153 remoteErr := responseErr{} 154 if err := json.Unmarshal(b, &remoteErr); err == nil { 155 if remoteErr.Err != "" { 156 return nil, &statusError{resp.StatusCode, serviceMethod, remoteErr.Err} 157 } 158 } 159 // old way... 160 return nil, &statusError{resp.StatusCode, serviceMethod, string(b)} 161 } 162 return resp.Body, nil 163 } 164 } 165 166 func backoff(retries int) time.Duration { 167 b, max := 1, defaultTimeOut 168 for b < max && retries > 0 { 169 b *= 2 170 retries-- 171 } 172 if b > max { 173 b = max 174 } 175 return time.Duration(b) * time.Second 176 } 177 178 func abort(start time.Time, timeOff time.Duration) bool { 179 return timeOff+time.Since(start) >= time.Duration(defaultTimeOut)*time.Second 180 } 181 182 func httpScheme(u *url.URL) string { 183 scheme := u.Scheme 184 if scheme != "https" { 185 scheme = "http" 186 } 187 return scheme 188 }