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