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  }