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