github.com/dcarley/cf-cli@v6.24.1-0.20170220111324-4225ff346898+incompatible/api/cloudcontroller/cloud_controller_connection.go (about)

     1  package cloudcontroller
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"encoding/json"
     8  	"io/ioutil"
     9  	"net"
    10  	"net/http"
    11  	"net/url"
    12  	"strings"
    13  	"time"
    14  )
    15  
    16  // CloudControllerConnection represents a connection to the Cloud Controller
    17  // server.
    18  type CloudControllerConnection struct {
    19  	HTTPClient *http.Client
    20  	UserAgent  string
    21  }
    22  
    23  // Config is for configuring a CloudControllerConnection.
    24  type Config struct {
    25  	DialTimeout       time.Duration
    26  	SkipSSLValidation bool
    27  }
    28  
    29  // NewConnection returns a new CloudControllerConnection with provided
    30  // configuration.
    31  func NewConnection(config Config) *CloudControllerConnection {
    32  	tr := &http.Transport{
    33  		TLSClientConfig: &tls.Config{
    34  			InsecureSkipVerify: config.SkipSSLValidation,
    35  		},
    36  		Proxy: http.ProxyFromEnvironment,
    37  		DialContext: (&net.Dialer{
    38  			KeepAlive: 30 * time.Second,
    39  			Timeout:   config.DialTimeout,
    40  		}).DialContext,
    41  	}
    42  
    43  	return &CloudControllerConnection{
    44  		HTTPClient: &http.Client{Transport: tr},
    45  	}
    46  }
    47  
    48  // Make performs the request and parses the response.
    49  func (connection *CloudControllerConnection) Make(request *http.Request, passedResponse *Response) error {
    50  	// In case this function is called from a retry, passedResponse may already
    51  	// be populated with a previous response. We reset in case there's an HTTP
    52  	// error and we don't repopulate it in populateResponse.
    53  	passedResponse.reset()
    54  
    55  	response, err := connection.HTTPClient.Do(request)
    56  	if err != nil {
    57  		return connection.processRequestErrors(request, err)
    58  	}
    59  
    60  	return connection.populateResponse(response, passedResponse)
    61  }
    62  
    63  func (connection *CloudControllerConnection) processRequestErrors(request *http.Request, err error) error {
    64  	switch e := err.(type) {
    65  	case *url.Error:
    66  		switch urlErr := e.Err.(type) {
    67  		case x509.UnknownAuthorityError:
    68  			return UnverifiedServerError{
    69  				URL: request.URL.String(),
    70  			}
    71  		case x509.HostnameError:
    72  			return SSLValidationHostnameError{
    73  				Message: urlErr.Error(),
    74  			}
    75  		default:
    76  			return RequestError{Err: e}
    77  		}
    78  	default:
    79  		return err
    80  	}
    81  }
    82  
    83  func (connection *CloudControllerConnection) populateResponse(response *http.Response, passedResponse *Response) error {
    84  	passedResponse.HTTPResponse = response
    85  
    86  	// The cloud controller returns warnings with key "X-Cf-Warnings", and the
    87  	// value is a comma seperated string.
    88  	if rawWarnings := response.Header.Get("X-Cf-Warnings"); rawWarnings != "" {
    89  		passedResponse.Warnings = []string{}
    90  		for _, warning := range strings.Split(rawWarnings, ",") {
    91  			warningTrimmed := strings.Trim(warning, " ")
    92  			passedResponse.Warnings = append(passedResponse.Warnings, warningTrimmed)
    93  		}
    94  	}
    95  
    96  	rawBytes, err := ioutil.ReadAll(response.Body)
    97  	defer response.Body.Close()
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	passedResponse.RawResponse = rawBytes
   103  
   104  	err = connection.handleStatusCodes(response, passedResponse)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	if passedResponse.Result != nil {
   110  		decoder := json.NewDecoder(bytes.NewBuffer(passedResponse.RawResponse))
   111  		decoder.UseNumber()
   112  		err = decoder.Decode(passedResponse.Result)
   113  		if err != nil {
   114  			return err
   115  		}
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  func (*CloudControllerConnection) handleStatusCodes(response *http.Response, passedResponse *Response) error {
   122  	if response.StatusCode >= 400 {
   123  		return RawHTTPStatusError{
   124  			StatusCode:  response.StatusCode,
   125  			RawResponse: passedResponse.RawResponse,
   126  			RequestIDs:  response.Header["X-Vcap-Request-Id"],
   127  		}
   128  	}
   129  
   130  	return nil
   131  }